LLVM プログラマーマニュアル

警告

これは常に進行中の作業です。

はじめに

このドキュメントは、LLVMソースベースで利用可能な重要なクラスとインターフェースのいくつかを強調することを目的としています。このマニュアルは、LLVMとは何か、どのように機能するのか、LLVMコードがどのように見えるのかを説明することを目的としたものではありません。これは、LLVMの基本を知っており、変換を作成したり、コードを分析または操作したりすることに関心があることを前提としています。

このドキュメントは、LLVMインフラストラクチャを構成する絶えず成長しているソースコードの中で自分の道を見つけることができるように、あなたを方向付けるはずです。このマニュアルは、ソースコードを読むための代替として機能することを目的としていないため、これらのクラスのいずれかに何かを行うためのメソッドがあるはずだと思うが、リストされていない場合は、ソースを確認してください。doxygenソースへのリンクは、これを可能な限り簡単にするために提供されています。

このドキュメントの最初のセクションでは、LLVMインフラストラクチャで作業する場合に知っておくと便利な一般情報を説明し、2番目のセクションでは、コアLLVMクラスについて説明します。将来的には、このマニュアルは、ドミネーター情報、CFGトラバーサルルーチン、および InstVisitor (doxygen) テンプレートなどの便利なユーティリティなどの拡張ライブラリの使用方法を説明する情報で拡張されます。

一般情報

このセクションには、LLVMソースベースで作業している場合に役立つ一般情報が含まれていますが、特定のAPIに固有のものではありません。

C++ 標準テンプレートライブラリ

LLVMは、C++ 標準テンプレートライブラリ (STL) を多用しており、おそらくあなたが慣れているよりもはるかに多く、またはこれまで見たことがあるよりもはるかに多く使用しています。このため、ライブラリで使用されている手法と機能を少し背景を読んでおくとよいでしょう。STLについて議論する多くの優れたページがあり、入手できるこのテーマに関するいくつかの本があるので、このドキュメントでは説明しません。

以下に役立つリンクをいくつか示します。

  1. cppreference.com - STLおよび標準C++ライブラリのその他の部分に関する優れたリファレンス。

  2. cplusplus.com - 上記のような別の優れたリファレンス。

  3. C++ In a Nutshell - これは製作中のO'Reillyの本です。Dinkumwareのものに匹敵するほどまともな標準ライブラリのリファレンスがありますが、本が出版されてから残念ながら無料ではなくなりました。

  4. C++に関するよくある質問.

  5. Bjarne StroustrupのC++ページ.

  6. Bruce Eckelの「Thinking in C++ 2nd ed. Volume 2」(できれば書籍を入手してください).

また、LLVMコーディング規約ガイドも参照することをお勧めします。このガイドは、波括弧をどこに置くかということよりも、保守しやすいコードを書く方法に焦点を当てています。

その他の役立つ参考資料

  1. プラットフォームをまたがる静的ライブラリと共有ライブラリの使用

重要で役立つLLVM API

ここでは、変換を記述する際に一般的に役立ち、知っておくと良いLLVM APIをいくつか紹介します。

isa<>cast<>dyn_cast<>テンプレート

LLVMソースベースでは、カスタム形式のRTTIが広範囲に使用されています。これらのテンプレートは、C++のdynamic_cast<>演算子と多くの類似点がありますが、いくつかの欠点(主にdynamic_cast<>がv-tableを持つクラスでのみ機能するという事実から生じる)はありません。これらのテンプレートは頻繁に使用されるため、その機能と仕組みを理解する必要があります。これらのテンプレートはすべて、llvm/Support/Casting.hdoxygen)ファイルで定義されています(このファイルを直接インクルードする必要はめったにないことに注意してください)。

isa<>:

isa<>演算子は、Javaの「instanceof」演算子とまったく同じように機能します。参照またはポインターが指定されたクラスのインスタンスを指しているかどうかに応じて、trueまたはfalseを返します。これは、さまざまな種類の制約チェックに非常に役立ちます(以下の例)。

cast<>:

cast<>演算子は、「チェック付きキャスト」操作です。ポインターまたは参照を基底クラスから派生クラスに変換します。実際には適切な型のインスタンスではない場合、アサーションエラーが発生します。これは、何かが適切な型であると信じる情報がある場合に使用する必要があります。isa<>テンプレートとcast<>テンプレートの例は次のとおりです。

static bool isLoopInvariant(const Value *V, const Loop *L) {
  if (isa<Constant>(V) || isa<Argument>(V) || isa<GlobalValue>(V))
    return true;

  // Otherwise, it must be an instruction...
  return !L->contains(cast<Instruction>(V)->getParent());
}

isa<>テストの後にcast<>を使用すべきではないことに注意してください。代わりに、dyn_cast<>演算子を使用してください。

dyn_cast<>:

dyn_cast<>演算子は、「チェック付きキャスト」操作です。オペランドが指定された型であるかどうかを確認し、そうであれば、そのポインターを返します(この演算子は参照では機能しません)。オペランドが正しい型でない場合は、nullポインターが返されます。したがって、これはC++のdynamic_cast<>演算子と非常によく似ており、同じ状況で使用する必要があります。通常、dyn_cast<>演算子は、次のようなifステートメントまたはその他のフロー制御ステートメントで使用されます。

if (auto *AI = dyn_cast<AllocationInst>(Val)) {
  // ...
}

この形式のifステートメントは、isa<>の呼び出しとcast<>の呼び出しを1つのステートメントに効果的に結合するものであり、非常に便利です。

dyn_cast<>演算子は、C++のdynamic_cast<>演算子やJavaのinstanceof演算子と同様に、乱用される可能性があることに注意してください。特に、さまざまなクラスの多くのバリアントをチェックするために、大きな連結されたif/then/elseブロックを使用しないでください。これを行いたい場合は、InstVisitorクラスを使用して命令の種類を直接ディスパッチする方がはるかにクリーンで効率的です。

isa_and_present<>:

isa_and_present<>演算子は、引数としてnullポインターを許可する(その場合、falseを返す)点を除いて、isa<>演算子とまったく同じように機能します。これは、いくつかのnullチェックを1つにまとめることができるため、役立つ場合があります。

cast_if_present<>:

cast_if_present<>演算子は、引数としてnullポインターを許可する(その場合、nullポインターを伝播する)点を除いて、cast<>演算子とまったく同じように機能します。これは、いくつかのnullチェックを1つにまとめることができるため、役立つ場合があります。

dyn_cast_if_present<>:

dyn_cast_if_present<>演算子は、引数としてnullポインターを許可する(その場合、nullポインターを伝播する)点を除いて、dyn_cast<>演算子とまったく同じように機能します。これは、いくつかのnullチェックを1つにまとめることができるため、役立つ場合があります。

これらの5つのテンプレートは、v-tableの有無にかかわらず、任意のクラスで使用できます。これらのテンプレートのサポートを追加する場合は、クラス階層にLLVMスタイルのRTTIを設定する方法のドキュメントを参照してください。

文字列の受け渡し(StringRefクラスとTwineクラス)

LLVMは一般的に文字列操作をあまり行いませんが、文字列を受け取る重要なAPIがいくつかあります。2つの重要な例は、命令、関数などに名前を持つValueクラスと、LLVMおよびClangで広範囲に使用されるStringMapクラスです。

これらはジェネリッククラスであり、null文字が埋め込まれている可能性のある文字列を受け入れる必要があります。したがって、単にconst char *を受け入れることはできず、const std::string&を受け入れるには、通常は不要なヒープ割り当てをクライアントが実行する必要があります。代わりに、多くのLLVM APIは、文字列を効率的に受け渡すために、StringRefまたはconst Twine&を使用します。

StringRefクラス

StringRefデータ型は、定数文字列(文字配列と長さ)への参照を表し、std::stringで使用可能な一般的な操作をサポートしますが、ヒープ割り当ては必要ありません。

Cスタイルのnull終端文字列、std::stringを使用して暗黙的に構築するか、文字ポインターと長さを使用して明示的に構築できます。たとえば、StringMapのfind関数は次のように宣言されています。

iterator find(StringRef Key);

クライアントは次のいずれかを使用して呼び出すことができます。

Map.find("foo");                 // Lookup "foo"
Map.find(std::string("bar"));    // Lookup "bar"
Map.find(StringRef("\0baz", 4)); // Lookup "\0baz"

同様に、文字列を返す必要があるAPIは、StringRefインスタンスを返すことができ、これは直接使用することも、strメンバー関数を使用してstd::stringに変換することもできます。詳細については、llvm/ADT/StringRef.hdoxygen)を参照してください。

StringRefクラスには外部メモリへのポインターが含まれているため、クラスのインスタンスを保存することは一般的に安全ではない(外部ストレージが解放されないことがわかっている場合を除く)ため、StringRefクラスを直接使用することはめったにありません。StringRefはLLVMでは小さくて広く普及しているため、常に値で渡す必要があります。

Twineクラス

Twinedoxygen)クラスは、APIが連結された文字列を受け入れる効率的な方法です。たとえば、一般的なLLVMパラダイムは、接尾辞を持つ別の命令の名前に基づいて1つの命令に名前を付けることです。例:

New = CmpInst::Create(..., SO->getName() + ".cmp");

Twineクラスは、実質的に一時的な(スタック割り当ての)オブジェクトを指す軽量なロープです。Twineは、文字列(つまり、C文字列、std::string、またはStringRef)にプラス演算子を適用した結果として暗黙的に構築できます。Twineは、実際に必要になるまで文字列の連結を遅延し、その時点で効率的に文字配列に直接レンダリングできます。これにより、文字列連結の一時的な結果を構築する際に不要なヒープ割り当てを回避できます。詳細については、llvm/ADT/Twine.h(doxygen)とこちらを参照してください。

StringRefと同様に、Twineオブジェクトは外部メモリを指しているため、直接保存または言及することはほとんどありません。これらは、連結された文字列を効率的に受け入れることができる関数を定義する場合にのみ使用することを目的としています。

文字列のフォーマット(formatv関数)

LLVMは必ずしも多くの文字列操作や解析を行うわけではありませんが、多くの文字列フォーマットを行います。診断メッセージから、llvm-readobjのようなllvmツールの出力、詳細な逆アセンブリリストの印刷、LLDBランタイムロギングまで、文字列フォーマットの必要性は至る所にあります。

formatvprintfに似た精神を持っていますが、PythonやC#から大きく借用した異なる構文を使用します。printfとは異なり、フォーマットする型をコンパイル時に推論するため、%dのようなフォーマット指定子を必要としません。これにより、特にsize_tやポインタ型のようなプラットフォーム固有の型の場合、移植可能なフォーマット文字列を構築しようとする際の精神的なオーバーヘッドが軽減されます。printfとPythonの両方とは異なり、LLVMが型のフォーマット方法を知らない場合は、コンパイルにも失敗します。これらの2つのプロパティにより、この関数は、printfファミリの関数のような従来の方法よりも安全で使いやすくなっています。

簡単なフォーマット

formatvの呼び出しには、0個以上の**置換シーケンス**で構成される単一の**フォーマット文字列**と、それに続く可変長の**置換値**のリストが含まれます。置換シーケンスは、{N[[,align]:style]}の形式の文字列です。

Nは、置換値のリストからの引数の0ベースのインデックスを参照します。つまり、同じパラメータを、場合によっては異なるスタイルや配置オプションを使用して、任意の順序で複数回参照できることに注意してください。

alignは、値をフォーマットするフィールドの幅と、フィールド内の値の配置を指定するオプションの文字列です。オプションの**配置スタイル**とそれに続く正の整数の**フィールド幅**として指定します。配置スタイルは、文字-(左揃え)、=(中央揃え)、または+(右揃え)のいずれかです。デフォルトは右揃えです。

styleは、値のフォーマットを制御する型固有のオプションの文字列です。たとえば、浮動小数点値をパーセンテージとしてフォーマットするには、スタイルオプションPを使用できます。

カスタムフォーマット

型のフォーマット動作をカスタマイズするには、2つの方法があります。

  1. 適切な静的フォーマットメソッドを使用して、型Tllvm::format_provider<T>のテンプレート特殊化を提供します。

namespace llvm {
  template<>
  struct format_provider<MyFooBar> {
    static void format(const MyFooBar &V, raw_ostream &Stream, StringRef Style) {
      // Do whatever is necessary to format `V` into `Stream`
    }
  };
  void foo() {
    MyFooBar X;
    std::string S = formatv("{0}", X);
  }
}

これは、独自のカスタム型を独自のカスタムスタイルオプションでフォーマットするためのサポートを追加するための便利な拡張メカニズムです。しかし、ライブラリがすでにフォーマット方法を知っている型のフォーマットメカニズムを拡張したい場合は役に立ちません。そのために、別のものが必要です。

  1. llvm::FormatAdapter<T>から継承する**フォーマットアダプタ**を提供します。

namespace anything {
  struct format_int_custom : public llvm::FormatAdapter<int> {
    explicit format_int_custom(int N) : llvm::FormatAdapter<int>(N) {}
    void format(llvm::raw_ostream &Stream, StringRef Style) override {
      // Do whatever is necessary to format ``this->Item`` into ``Stream``
    }
  };
}
namespace llvm {
  void foo() {
    std::string S = formatv("{0}", anything::format_int_custom(42));
  }
}

型がFormatAdapter<T>から派生していることが検出された場合、formatvは指定されたスタイルを渡して、引数に対してformatメソッドを呼び出します。これにより、ビルトインフォーマットプロバイダがすでに存在する型を含め、任意の型のカスタムフォーマットを提供できます。

formatvの例

以下は、formatvの使用法を示す不完全な例のセットを提供することを目的としています。詳細については、doxygenドキュメントを読むか、ユニットテストスイートを参照してください。

std::string S;
// Simple formatting of basic types and implicit string conversion.
S = formatv("{0} ({1:P})", 7, 0.35);  // S == "7 (35.00%)"

// Out-of-order referencing and multi-referencing
outs() << formatv("{0} {2} {1} {0}", 1, "test", 3); // prints "1 3 test 1"

// Left, right, and center alignment
S = formatv("{0,7}",  'a');  // S == "      a";
S = formatv("{0,-7}", 'a');  // S == "a      ";
S = formatv("{0,=7}", 'a');  // S == "   a   ";
S = formatv("{0,+7}", 'a');  // S == "      a";

// Custom styles
S = formatv("{0:N} - {0:x} - {1:E}", 12345, 123908342); // S == "12,345 - 0x3039 - 1.24E8"

// Adapters
S = formatv("{0}", fmt_align(42, AlignStyle::Center, 7));  // S == "  42   "
S = formatv("{0}", fmt_repeat("hi", 3)); // S == "hihihi"
S = formatv("{0}", fmt_pad("hi", 2, 6)); // S == "  hi      "

// Ranges
std::vector<int> V = {8, 9, 10};
S = formatv("{0}", make_range(V.begin(), V.end())); // S == "8, 9, 10"
S = formatv("{0:$[+]}", make_range(V.begin(), V.end())); // S == "8+9+10"
S = formatv("{0:$[ + ]@[x]}", make_range(V.begin(), V.end())); // S == "0x8 + 0x9 + 0xA"

エラー処理

適切なエラー処理は、コード内のバグを特定するのに役立ち、エンドユーザーがツールの使用におけるエラーを理解するのに役立ちます。エラーは、*プログラム的*エラーと*回復可能*エラーの2つの広いカテゴリに分類され、処理と報告の方法が異なります。

プログラム的エラー

プログラム的エラーは、プログラムの不変条件またはAPIコントラクトの違反であり、プログラム自体のバグを表します。私たちの目的は、不変条件を文書化し、実行時に不変条件が破られた場合に、失敗時点で(いくつかの基本的な診断を提供して)迅速に中止することです。

プログラム的エラーを処理するための基本的なツールは、アサーションとllvm_unreachable関数です。アサーションは、不変条件を表現するために使用され、不変条件を説明するメッセージを含める必要があります。

assert(isPhysReg(R) && "All virt regs should have been allocated already.");

llvm_unreachable関数は、プログラムの不変条件が保持されている場合に決して入力されるべきではない制御フローの領域を文書化するために使用できます。

enum { Foo, Bar, Baz } X = foo();

switch (X) {
  case Foo: /* Handle Foo */; break;
  case Bar: /* Handle Bar */; break;
  default:
    llvm_unreachable("X should be Foo or Bar here");
}

回復可能エラー

回復可能エラーは、プログラムの環境におけるエラー、たとえば、リソース障害(ファイルが見つからない、ネットワーク接続が切断されたなど)や、不正な入力などを表します。これらのエラーは、プログラムの適切なレベルで検出して伝達し、適切に処理できるようにする必要があります。エラーの処理は、問題をユーザーに報告するだけの場合もあれば、回復を試みることもあります。

LLVM全体でこのエラー処理スキームを使用するのが理想的ですが、適用するのが実際的ではない場所もあります。非プログラム的エラーをどうしても出力する必要があり、Errorモデルが機能しない状況では、report_fatal_errorを呼び出すことができます。これにより、インストールされたエラーハンドラが呼び出され、メッセージが出力され、プログラムが中止されます。この場合、report_fatal_errorの使用は推奨されません。

回復可能エラーは、LLVMのErrorスキームを使用してモデル化されます。このスキームは、古典的なC整数エラーコードやC++のstd::error_codeと同様に、関数の戻り値を使用してエラーを表します。ただし、Errorクラスは実際にはユーザー定義のエラー型の軽量ラッパーであり、エラーを説明するために任意の情報を添付できます。これは、C++例外がユーザー定義型をスローできる方法に似ています。

成功値は、Error::success()を呼び出して作成されます。例:

Error foo() {
  // Do something.
  // Return success.
  return Error::success();
}

成功値の構築と返却は非常に安価です。プログラムのパフォーマンスへの影響は最小限です。

失敗値は、make_error<T>を使用して構築されます。ここで、TはErrorInfoユーティリティから継承する任意のクラスです。例:

class BadFileFormat : public ErrorInfo<BadFileFormat> {
public:
  static char ID;
  std::string Path;

  BadFileFormat(StringRef Path) : Path(Path.str()) {}

  void log(raw_ostream &OS) const override {
    OS << Path << " is malformed";
  }

  std::error_code convertToErrorCode() const override {
    return make_error_code(object_error::parse_failed);
  }
};

char BadFileFormat::ID; // This should be declared in the C++ file.

Error printFormattedFile(StringRef Path) {
  if (<check for valid format>)
    return make_error<BadFileFormat>(Path);
  // print file contents.
  return Error::success();
}

エラー値は暗黙的にブール値に変換できます。エラーの場合はtrue、成功の場合はfalseであり、次のイディオムが可能になります。

Error mayFail();

Error foo() {
  if (auto Err = mayFail())
    return Err;
  // Success! We can proceed.
  ...

失敗する可能性があるが値を返す必要がある関数の場合、Expected<T>ユーティリティを使用できます。この型の値は、TまたはErrorのいずれかで構築できます。Expected<T>の値も暗黙的にブール値に変換できますが、Errorとは反対の規則で変換されます。成功の場合はtrue、エラーの場合はfalseです。成功した場合、T値は逆参照演算子を使用してアクセスできます。失敗した場合、Error値はtakeError()メソッドを使用して抽出できます。慣用的な使用法は次のようになります。

Expected<FormattedFile> openFormattedFile(StringRef Path) {
  // If badly formatted, return an error.
  if (auto Err = checkFormat(Path))
    return std::move(Err);
  // Otherwise return a FormattedFile instance.
  return FormattedFile(Path);
}

Error processFormattedFile(StringRef Path) {
  // Try to open a formatted file
  if (auto FileOrErr = openFormattedFile(Path)) {
    // On success, grab a reference to the file and continue.
    auto &File = *FileOrErr;
    ...
  } else
    // On error, extract the Error value and return it.
    return FileOrErr.takeError();
}

Expected<T>の値が成功モードの場合、takeError()メソッドは成功値を返します。この事実を利用すると、上記の関数は次のように書き換えることができます。

Error processFormattedFile(StringRef Path) {
  // Try to open a formatted file
  auto FileOrErr = openFormattedFile(Path);
  if (auto Err = FileOrErr.takeError())
    // On error, extract the Error value and return it.
    return Err;
  // On success, grab a reference to the file and continue.
  auto &File = *FileOrErr;
  ...
}

この2番目の形式は、必要なインデントを制限するため、複数のExpected<T>の値を含む関数では、より読みやすくなることがよくあります。

Expected<T>の値が既存の変数に移動される場合、moveInto()メソッドは余分な変数を名前付けする必要性を回避します。これは、Expected<T>の値がポインタのようなセマンティクスを持つ場合に、operator->()を有効にするために役立ちます。たとえば

Expected<std::unique_ptr<MemoryBuffer>> openBuffer(StringRef Path);
Error processBuffer(StringRef Buffer);

Error processBufferAtPath(StringRef Path) {
  // Try to open a buffer.
  std::unique_ptr<MemoryBuffer> MB;
  if (auto Err = openBuffer(Path).moveInto(MB))
    // On error, return the Error value.
    return Err;
  // On success, use MB.
  return processBuffer(MB->getBuffer());
}

この3番目の形式は、T&&から代入可能な任意の型で動作します。これは、Expected<T>の値を既に宣言されているOptional<T>に格納する必要がある場合に役立ちます。たとえば

Expected<StringRef> extractClassName(StringRef Definition);
struct ClassData {
  StringRef Definition;
  Optional<StringRef> LazyName;
  ...
  Error initialize() {
    if (auto Err = extractClassName(Path).moveInto(LazyName))
      // On error, return the Error value.
      return Err;
    // On success, LazyName has been initialized.
    ...
  }
};

成功または失敗に関わらず、すべてのErrorインスタンスは、破棄される前にチェックするか、(std::moveまたはreturnを介して)移動する必要があります。チェックされていないエラーを誤って破棄すると、チェックされていない値のデストラクタが実行される時点でプログラムがアボートし、この規則の違反を特定して修正するのが容易になります。

成功値は、(ブール変換演算子を呼び出して)テストされるとチェック済みと見なされます。

if (auto Err = mayFail(...))
  return Err; // Failure value - move error to caller.

// Safe to continue: Err was checked.

対照的に、次のコードは、mayFailが成功値を返した場合でも、常にアボートを引き起こします。

mayFail();
// Program will always abort here, even if mayFail() returns Success, since
// the value is not checked.

失敗値は、エラー型のハンドラーがアクティブ化されるとチェック済みと見なされます。

handleErrors(
  processFormattedFile(...),
  [](const BadFileFormat &BFF) {
    report("Unable to process " + BFF.Path + ": bad format");
  },
  [](const FileNotFound &FNF) {
    report("File not found " + FNF.Path);
  });

handleErrors関数は、最初のエラーを引数として受け取り、その後に可変個の「ハンドラー」のリストを受け取ります。各ハンドラーは、1つの引数を持つ呼び出し可能な型(関数、ラムダ、または呼び出し演算子を持つクラス)である必要があります。handleErrors関数は、シーケンス内の各ハンドラーを訪問し、エラーの動的な型に対して引数の型をチェックし、最初に一致するハンドラーを実行します。これは、C++例外に対してどのcatch句を実行するかを決定するために使用されるのと同じ決定プロセスです。

handleErrorsに渡されるハンドラーのリストは、発生する可能性のあるすべてのエラー型を網羅していない可能性があるため、handleErrors関数は、チェックまたは伝播する必要があるError値も返します。handleErrorsに渡されるエラー値がどのハンドラーにも一致しない場合、handleErrorsから返されます。したがって、handleErrorsの慣用的な使用法は次のようになります。

if (auto Err =
      handleErrors(
        processFormattedFile(...),
        [](const BadFileFormat &BFF) {
          report("Unable to process " + BFF.Path + ": bad format");
        },
        [](const FileNotFound &FNF) {
          report("File not found " + FNF.Path);
        }))
  return Err;

ハンドラーリストが網羅的であることが本当にわかっている場合は、代わりにhandleAllErrors関数を使用できます。これは、未処理のエラーが渡された場合にプログラムを終了するという点を除いて、handleErrorsと同じであり、したがってvoidを返すことができます。handleAllErrors関数は一般的に避ける必要があります。プログラムの別の場所で新しいエラー型を導入すると、以前は網羅的だったエラーのリストが網羅的でなくなり、予期しないプログラムの終了のリスクが生じます。可能な場合は、handleErrorsを使用し、不明なエラーをスタックに伝播してください。

エラーメッセージを出力してからエラーコードで終了することでエラーを処理できるツールコードの場合、フォールバック可能な関数を呼び出す際の制御フローを簡素化するため、ExitOnErrorユーティリティがhandleErrorsよりも適切な選択肢となる可能性があります。

フォールバック可能な関数への特定の呼び出しが常に成功することがわかっている状況(たとえば、既知の安全な入力を持つ入力のサブセットでのみ失敗する可能性のある関数への呼び出し)では、cantFail関数を使用してエラー型を削除し、制御フローを簡素化できます。

StringError

多くの種類のエラーには回復戦略がなく、取ることができる唯一のアクションは、ユーザーが環境を修正できるように、ユーザーにそれらを報告することです。この場合、エラーを文字列として表現することは理にかなっています。LLVMは、この目的のためにStringErrorクラスを提供します。これは、2つの引数を取ります。文字列エラーメッセージと、相互運用性のための同等のstd::error_codeです。また、このクラスの一般的な使用法を簡素化するためのcreateStringError関数も提供します。

// These two lines of code are equivalent:
make_error<StringError>("Bad executable", errc::executable_format_error);
createStringError(errc::executable_format_error, "Bad executable");

作成しているエラーをstd::error_codeに変換する必要がないことが確実な場合は、inconvertibleErrorCode()関数を使用できます。

createStringError(inconvertibleErrorCode(), "Bad executable");

これは、慎重に検討した後にのみ行う必要があります。このエラーをstd::error_codeに変換しようとすると、プログラムが即座に終了します。エラーに相互運用性が必要ないことが確実でない限り、変換できる既存のstd::error_codeを探し、応急措置として新しいものを導入することさえ検討する必要があります(それは苦痛なことですが)。

createStringErrorは、書式設定されたメッセージを提供するために、printfスタイルの書式指定子を使用できます。

createStringError(errc::executable_format_error,
                  "Bad executable: %s", FileName);
std::error_codeおよびErrorOrとの相互運用性

多くの既存のLLVM APIは、std::error_codeとそのパートナーであるErrorOr<T>Expected<T>と同じ役割を果たしますが、Errorではなくstd::error_codeをラップします)を使用しています。エラー型の伝染性により、これらの関数のいずれかをErrorまたはExpected<T>を返すように変更しようとすると、多くの場合、呼び出し側、呼び出し側の呼び出し側などへの変更が雪崩のように発生します。(MachOObjectFileのコンストラクタからErrorを返す最初の試みは、差分が3000行に達し、半ダースのライブラリに影響を与え、それでもまだ増え続けていたため、中止されました)。

この問題を解決するために、Error/std::error_codeの相互運用性要件が導入されました。2つの関数のペアを使用すると、任意のError値をstd::error_codeに変換し、任意のExpected<T>ErrorOr<T>に変換し、その逆も可能です。

std::error_code errorToErrorCode(Error Err);
Error errorCodeToError(std::error_code EC);

template <typename T> ErrorOr<T> expectedToErrorOr(Expected<T> TOrErr);
template <typename T> Expected<T> errorOrToExpected(ErrorOr<T> TOrEC);

これらのAPIを使用すると、std::error_codeからError、およびErrorOr<T>からExpected<T>に個々の関数を更新する外科的なパッチを簡単に作成できます。

エラーハンドラーからエラーを返す

エラー回復の試み自体が失敗する可能性があります。そのため、handleErrorsは、実際には3つの異なる形式のハンドラーシグネチャを認識します。

// Error must be handled, no new errors produced:
void(UserDefinedError &E);

// Error must be handled, new errors can be produced:
Error(UserDefinedError &E);

// Original error can be inspected, then re-wrapped and returned (or a new
// error can be produced):
Error(std::unique_ptr<UserDefinedError> E);

ハンドラーから返されるエラーは、それ自体で処理されるか、スタックに伝播できるように、handleErrors関数から返されます。

ExitOnErrorを使用してツールコードを簡素化する

ライブラリコードは、回復可能なエラーに対してexitを呼び出すべきではありませんが、ツールコード(特にコマンドラインツール)では、これは妥当なアプローチになる可能性があります。エラーが発生したときにexitを呼び出すと、エラーをスタックに伝播する必要がなくなるため、制御フローが大幅に簡素化されます。これにより、各フォールバック可能な呼び出しがexitのチェックと呼び出しでラップされている限り、コードを直線的なスタイルで記述できます。ExitOnErrorクラスは、成功した場合にエラーを削除し、失敗した場合にstderrにログを記録してから終了する呼び出し演算子を提供することにより、このパターンをサポートします。

このクラスを使用するには、プログラムでグローバルなExitOnError変数を宣言します。

ExitOnError ExitOnErr;

フォールバック可能な関数への呼び出しは、ExitOnErrの呼び出しでラップすることができ、これにより、フォールバックしない呼び出しになります。

Error mayFail();
Expected<int> mayFail2();

void foo() {
  ExitOnErr(mayFail());
  int X = ExitOnErr(mayFail2());
}

失敗した場合、エラーのログメッセージがstderrに書き込まれます。オプションで、setBannerメソッドを呼び出して設定できる文字列「バナー」が前に付く場合があります。また、setExitCodeMapperメソッドを使用して、Error値から終了コードへのマッピングを提供することもできます。

int main(int argc, char *argv[]) {
  ExitOnErr.setBanner(std::string(argv[0]) + " error:");
  ExitOnErr.setExitCodeMapper(
    [](const Error &Err) {
      if (Err.isA<BadFileFormat>())
        return 2;
      return 1;
    });

可能な場合はツールコードでExitOnErrorを使用してください。これにより、可読性が大幅に向上する可能性があります。

cantFailを使用して安全な呼び出しサイトを簡素化する

一部の関数は入力のサブセットに対してのみ失敗する可能性があるため、既知の安全な入力を使用する呼び出しは成功すると想定できます。

cantFail関数は、引数が成功値であるというアサーションをラップし、Expected<T>の場合は、T値をアンラップすることにより、これをカプセル化します。

Error onlyFailsForSomeXValues(int X);
Expected<int> onlyFailsForSomeXValues2(int X);

void foo() {
  cantFail(onlyFailsForSomeXValues(KnownSafeValue));
  int Y = cantFail(onlyFailsForSomeXValues2(KnownSafeValue));
  ...
}

ExitOnErrorユーティリティと同様に、cantFailは制御フローを簡略化します。しかし、エラーケースの扱い方は大きく異なります。ExitOnErrorはエラー入力があった場合にプログラムを確実に終了させるのに対し、cantFailは単に結果が成功であることをアサートします。デバッグビルドでは、エラーが発生した場合、アサーション失敗となります。リリースビルドでは、失敗値に対するcantFailの動作は未定義です。そのため、cantFailの使用には注意が必要です。クライアントは、cantFailでラップされた呼び出しが、与えられた引数で本当に失敗しないことを確信する必要があります。

cantFail関数の使用はライブラリコードでは稀であるべきですが、入力やモックアップされたクラスや関数が安全であることがわかっているツールや単体テストコードでは、より有用である可能性があります。

失敗する可能性のあるコンストラクタ

一部のクラスでは、リソースの取得や、コンストラクション中に失敗する可能性のある他の複雑な初期化が必要です。残念ながら、コンストラクタはエラーを返すことができず、クライアントがオブジェクトが構築された後にオブジェクトをテストして有効であることを確認することは、テストを忘れるのが非常に簡単であるため、エラーが発生しやすくなります。この問題を回避するには、名前付きコンストラクタのイディオムを使用し、Expected<T>を返します。

class Foo {
public:

  static Expected<Foo> Create(Resource R1, Resource R2) {
    Error Err = Error::success();
    Foo F(R1, R2, Err);
    if (Err)
      return std::move(Err);
    return std::move(F);
  }

private:

  Foo(Resource R1, Resource R2, Error &Err) {
    ErrorAsOutParameter EAO(&Err);
    if (auto Err2 = R1.acquire()) {
      Err = std::move(Err2);
      return;
    }
    Err = R2.acquire();
  }
};

ここで、名前付きコンストラクタは、Errorをリファレンスで実際のコンストラクタに渡し、コンストラクタはそれを使用してエラーを返すことができます。ErrorAsOutParameterユーティリティは、エラーを割り当てることができるように、コンストラクタへのエントリ時にError値のcheckedフラグを設定し、次に、クライアント(名前付きコンストラクタ)にエラーをチェックさせるために、終了時にリセットします。

このイディオムを使用することで、Fooを構築しようとするクライアントは、適切に形成されたFooまたはErrorのいずれかを受け取り、無効な状態のオブジェクトを受け取ることはありません。

型に基づいてエラーを伝播および消費する

一部のコンテキストでは、特定のエラー型は無害であることが知られています。たとえば、アーカイブをウォークするとき、一部のクライアントは、ウォークをすぐに終了するのではなく、形式が正しくないオブジェクトファイルをスキップすることを好む場合があります。形式が正しくないオブジェクトをスキップすることは、手の込んだハンドラーメソッドを使用して実現できますが、Error.hヘッダーには、このイディオムをはるかにクリーンにする2つのユーティリティ、型検査メソッドのisAと、consumeError関数が用意されています。

Error walkArchive(Archive A) {
  for (unsigned I = 0; I != A.numMembers(); ++I) {
    auto ChildOrErr = A.getMember(I);
    if (auto Err = ChildOrErr.takeError()) {
      if (Err.isA<BadFileFormat>())
        consumeError(std::move(Err))
      else
        return Err;
    }
    auto &Child = *ChildOrErr;
    // Use Child
    ...
  }
  return Error::success();
}
joinErrorsを使用したエラーの連結

上記のアーカイブウォークの例では、BadFileFormatエラーは単に消費されて無視されます。クライアントがアーカイブのウォーク完了後にこれらのエラーをレポートしたい場合は、joinErrorsユーティリティを使用できます。

Error walkArchive(Archive A) {
  Error DeferredErrs = Error::success();
  for (unsigned I = 0; I != A.numMembers(); ++I) {
    auto ChildOrErr = A.getMember(I);
    if (auto Err = ChildOrErr.takeError())
      if (Err.isA<BadFileFormat>())
        DeferredErrs = joinErrors(std::move(DeferredErrs), std::move(Err));
      else
        return Err;
    auto &Child = *ChildOrErr;
    // Use Child
    ...
  }
  return DeferredErrs;
}

joinErrorsルーチンは、ユーザー定義のエラーのリストを保持するErrorListという特別なエラー型を構築します。handleErrorsルーチンはこの型を認識し、含まれる各エラーを順番に処理しようとします。含まれるすべてのエラーを処理できる場合、handleErrorsError::success()を返します。それ以外の場合、handleErrorsは残りのエラーを連結し、結果のErrorListを返します。

失敗する可能性のあるイテレータとイテレータ範囲の構築

上記のアーカイブウォークの例では、インデックスによってアーカイブメンバーを取得しますが、これには反復とエラーチェックのためにかなりのボイラープレートが必要です。Archiveのような失敗する可能性のあるコンテナに対して、次の自然な反復イディオムをサポートする「失敗する可能性のあるイテレータ」パターンを使用することで、これをクリーンアップできます。

Error Err = Error::success();
for (auto &Child : Ar->children(Err)) {
  // Use Child - only enter the loop when it's valid

  // Allow early exit from the loop body, since we know that Err is success
  // when we're inside the loop.
  if (BailOutOn(Child))
    return;

  ...
}
// Check Err after the loop to ensure it didn't break due to an error.
if (Err)
  return Err;

このイディオムを有効にするために、失敗する可能性のあるコンテナのイテレータは自然なスタイルで記述され、++および--演算子は、失敗する可能性のあるError inc()およびError dec()関数に置き換えられます。例えば、

class FallibleChildIterator {
public:
  FallibleChildIterator(Archive &A, unsigned ChildIdx);
  Archive::Child &operator*();
  friend bool operator==(const ArchiveIterator &LHS,
                         const ArchiveIterator &RHS);

  // operator++/operator-- replaced with fallible increment / decrement:
  Error inc() {
    if (!A.childValid(ChildIdx + 1))
      return make_error<BadArchiveMember>(...);
    ++ChildIdx;
    return Error::success();
  }

  Error dec() { ... }
};

この種の失敗する可能性のあるイテレータインターフェースのインスタンスは、失敗する可能性のあるイテレータユーティリティでラップされ、構築時にラッパーに渡された参照を介してエラーを返すoperator++およびoperator--を提供します。fallible_iteratorラッパーは、(a)エラー時に範囲の最後にジャンプし、(b)イテレータがendと比較されて等しくないと判断されたときは常にエラーをチェック済みとしてマークする(特に、これにより、範囲ベースのforループの本体全体でエラーがチェック済みとしてマークされます)。これにより、冗長なエラーチェックなしでループから早期に抜け出すことができます。

失敗する可能性のあるイテレータインターフェース(例:上記のFallibleChildIterator)のインスタンスは、make_fallible_itr関数およびmake_fallible_end関数を使用してラップされます。例えば、

class Archive {
public:
  using child_iterator = fallible_iterator<FallibleChildIterator>;

  child_iterator child_begin(Error &Err) {
    return make_fallible_itr(FallibleChildIterator(*this, 0), Err);
  }

  child_iterator child_end() {
    return make_fallible_end(FallibleChildIterator(*this, size()));
  }

  iterator_range<child_iterator> children(Error &Err) {
    return make_range(child_begin(Err), child_end());
  }
};

fallible_iteratorユーティリティを使用すると、(失敗するincおよびdec操作を使用して)失敗する可能性のあるイテレータを自然に構築でき、c++イテレータ/ループイディオムを比較的自然に使用できます。

Errorとその関連ユーティリティの詳細については、Error.hヘッダーファイルを参照してください。

関数およびその他の呼び出し可能なオブジェクトの受け渡し

関数にコールバックオブジェクトを渡したい場合があります。ラムダ式や他の関数オブジェクトをサポートするため、関数ポインタと不透明なクッキーを受け取る従来のCアプローチを使用すべきではありません。

void takeCallback(bool (*Callback)(Function *, void *), void *Cookie);

代わりに、次のいずれかのアプローチを使用します。

関数テンプレート

関数の定義をヘッダーファイルに入れたくない場合は、呼び出し可能な型でテンプレート化された関数テンプレートにします。

template<typename Callable>
void takeCallback(Callable Callback) {
  Callback(1, 2, 3);
}

function_refクラステンプレート

function_ref (doxygen) クラステンプレートは、呼び出し可能なオブジェクトへの参照を表し、呼び出し可能なオブジェクトの型でテンプレート化されます。これは、関数が戻った後にコールバックを保持する必要がない場合に、関数にコールバックを渡すのに適した選択肢です。このように、function_refstd::functionに対するStringRefstd::stringに対する関係と同様です。

function_ref<Ret(Param1, Param2, ...)>は、Param1Param2、...型の引数で呼び出すことができ、Ret型に変換できる値を返す任意の呼び出し可能なオブジェクトから暗黙的に構築できます。例えば、

void visitBasicBlocks(Function *F, function_ref<bool (BasicBlock*)> Callback) {
  for (BasicBlock &BB : *F)
    if (Callback(&BB))
      return;
}

以下を使用して呼び出すことができます。

visitBasicBlocks(F, [&](BasicBlock *BB) {
  if (process(BB))
    return isEmpty(BB);
  return false;
});

function_refオブジェクトには外部メモリへのポインタが含まれているため、クラスのインスタンスを格納することは一般的に安全ではありません(外部ストレージが解放されないことがわかっている場合を除く)。この機能が必要な場合は、std::functionの使用を検討してください。function_refは十分に小さいため、常に値で渡す必要があります。

LLVM_DEBUG()マクロと-debugオプション

パスに取り組んでいるとき、多くの場合、デバッグ用のプリントアウトやその他のコードをパスに入れます。動作するようになったら、削除する必要がありますが、将来(発生する可能性のある新しいバグを解決するために)再度必要になる可能性があります。

当然のことながら、このため、デバッグ用のプリントアウトを削除したくはありませんが、常にノイズが多くなることを望んでいません。標準的な妥協案は、それらをコメントアウトすることです。これにより、将来必要になった場合に有効にすることができます。

llvm/Support/Debug.h (doxygen) ファイルは、この問題に対してはるかに優れたソリューションであるLLVM_DEBUG()という名前のマクロを提供します。基本的に、任意のコードをLLVM_DEBUGマクロの引数に入れることができます。そして、'opt'(または他のツール)が'-debug'コマンドライン引数を付けて実行された場合にのみ実行されます。

LLVM_DEBUG(dbgs() << "I am here!\n");

次に、次のようにパスを実行できます。

$ opt < a.bc > /dev/null -mypass
<no output>
$ opt < a.bc > /dev/null -mypass -debug
I am here!

独自のソリューションではなくLLVM_DEBUG()マクロを使用すると、パスのデバッグ出力用に「別の」コマンドラインオプションを作成する必要がなくなります。LLVM_DEBUG()マクロはアサート以外のビルドでは無効になっているため、パフォーマンスにまったく影響を与えません(同じ理由で、副作用も含まれていない必要があります!)。

LLVM_DEBUG()マクロのもう一つの利点は、gdbで直接有効/無効を切り替えられることです。プログラム実行中であれば、gdbから「set DebugFlag=0」または「set DebugFlag=1」を使用するだけです。プログラムがまだ開始されていない場合は、-debugを付けて実行することもできます。

DEBUG_TYPE-debug-onlyオプションによる詳細なデバッグ情報

-debugを有効にすると、情報が多すぎる(コードジェネレータを操作している場合など)状況に陥ることがあります。より詳細な制御でデバッグ情報を有効にしたい場合は、DEBUG_TYPEマクロを定義し、次のように-debug-onlyオプションを使用する必要があります。

#define DEBUG_TYPE "foo"
LLVM_DEBUG(dbgs() << "'foo' debug type\n");
#undef  DEBUG_TYPE
#define DEBUG_TYPE "bar"
LLVM_DEBUG(dbgs() << "'bar' debug type\n");
#undef  DEBUG_TYPE

次に、次のようにパスを実行できます。

$ opt < a.bc > /dev/null -mypass
<no output>
$ opt < a.bc > /dev/null -mypass -debug
'foo' debug type
'bar' debug type
$ opt < a.bc > /dev/null -mypass -debug-only=foo
'foo' debug type
$ opt < a.bc > /dev/null -mypass -debug-only=bar
'bar' debug type
$ opt < a.bc > /dev/null -mypass -debug-only=foo,bar
'foo' debug type
'bar' debug type

実際には、モジュール全体のデバッグタイプを指定するために、ファイルの先頭でのみDEBUG_TYPEを設定する必要があります。Debug.hを含めた後、ヘッダーの#includeの周囲では設定しないように注意してください。また、名前が競合しないことを保証するシステムがないため、「foo」や「bar」よりも意味のある名前を使用する必要があります。2つの異なるモジュールが同じ文字列を使用すると、名前が指定されたときに両方ともオンになります。これにより、たとえば、ソースが複数のファイルにある場合でも、-debug-only=InstrSchedを使用して、命令スケジューリングに関するすべてのデバッグ情報を有効にすることができます。名前にはコンマ(,)を含めることはできません。これは、-debug-onlyオプションの引数を区切るために使用されるためです。

パフォーマンス上の理由から、-debug-onlyはLLVMの最適化ビルド(--enable-optimized)では使用できません。

DEBUG_WITH_TYPEマクロは、DEBUG_TYPEを設定したいが、特定のDEBUGステートメントに対してのみ設定したい場合にも利用できます。追加の最初のパラメータとして、使用するタイプを指定します。たとえば、上記の例は次のように記述できます。

DEBUG_WITH_TYPE("foo", dbgs() << "'foo' debug type\n");
DEBUG_WITH_TYPE("bar", dbgs() << "'bar' debug type\n");

Statisticクラスと-statsオプション

llvm/ADT/Statistic.hdoxygen)ファイルは、LLVMコンパイラが何をしているか、およびさまざまな最適化がどれだけ効果的かを追跡するための統一された方法として使用されるStatisticという名前のクラスを提供します。特定のプログラムを高速化するためにどの最適化が貢献しているかを確認するのに役立ちます。

多くの場合、大規模なプログラムでパスを実行して、特定の変換が何回行われたかを確認したい場合があります。手動で調べたり、アドホックな方法を使用したりすることもできますが、これは非常に面倒で、大規模なプログラムにはあまり役立ちません。Statisticクラスを使用すると、この情報を非常に簡単に追跡でき、計算された情報は、実行されている残りのパスと同じように均一な方法で表示されます。

Statisticの使用例は多数ありますが、基本的な使用方法は次のとおりです。

次のように統計情報を定義します。

#define DEBUG_TYPE "mypassname"   // This goes after any #includes.
STATISTIC(NumXForms, "The # of times I did stuff");

STATISTICマクロは、最初の引数で指定された名前の静的変数を定義します。パス名はDEBUG_TYPEマクロから取得され、説明は2番目の引数から取得されます。定義された変数(この場合は「NumXForms」)は、符号なし整数のように動作します。

変換を行うたびに、カウンタをインクリメントします。

++NumXForms;   // I did stuff!

これだけです。統計情報を出力するために「opt」を使用するには、「-stats」オプションを使用します。

$ opt -stats -mypassname < program.bc > /dev/null
... statistics output ...

-stats」オプションを使用するには、アサーションが有効になっている状態でLLVMをコンパイルする必要があることに注意してください。

SPECベンチマークスイートのCファイルでoptを実行すると、次のようなレポートが出力されます。

  7646 bitcodewriter   - Number of normal instructions
   725 bitcodewriter   - Number of oversized instructions
129996 bitcodewriter   - Number of bitcode bytes written
  2817 raise           - Number of insts DCEd or constprop'd
  3213 raise           - Number of cast-of-self removed
  5046 raise           - Number of expression trees converted
    75 raise           - Number of other getelementptr's formed
   138 raise           - Number of load/store peepholes
    42 deadtypeelim    - Number of unused typenames removed from symtab
   392 funcresolve     - Number of varargs functions resolved
    27 globaldce       - Number of global variables removed
     2 adce            - Number of basic blocks removed
   134 cee             - Number of branches revectored
    49 cee             - Number of setcc instruction eliminated
   532 gcse            - Number of loads removed
  2919 gcse            - Number of instructions removed
    86 indvars         - Number of canonical indvars added
    87 indvars         - Number of aux indvars removed
    25 instcombine     - Number of dead inst eliminate
   434 instcombine     - Number of insts combined
   248 licm            - Number of load insts hoisted
  1298 licm            - Number of insts hoisted to a loop pre-header
     3 licm            - Number of insts hoisted to multiple loop preds (bad, no loop pre-header)
    75 mem2reg         - Number of alloca's promoted
  1444 cfgsimplify     - Number of blocks simplified

明らかに、非常に多くの最適化があるため、この種のものを統合するためのフレームワークがあることは非常に便利です。パスをフレームワークによく適合させることで、保守性と有用性が向上します。

コードのデバッグを支援するためのデバッグカウンタの追加

新しいパスを作成したり、バグを追跡したりするとき、パス内の特定のことが発生するかどうかを制御できると便利な場合があります。たとえば、最小化ツールが大きなテストケースしか簡単に提供できない場合があります。バイセクションを使用して、特定の変換が発生するかどうかを自動的に絞り込みたい場合があります。ここで、デバッグカウンタが役立ちます。コードの一部を特定の回数だけ実行するためのフレームワークを提供します。

llvm/Support/DebugCounter.hdoxygen)ファイルは、コードの一部を実行制御するコマンドラインカウンタオプションを作成するために使用できるDebugCounterという名前のクラスを提供します。

次のようにDebugCounterを定義します。

DEBUG_COUNTER(DeleteAnInstruction, "passname-delete-instruction",
              "Controls which instructions get delete");

DEBUG_COUNTERマクロは、最初の引数で指定された名前の静的変数を定義します。カウンタの名前(コマンドラインで使用)は2番目の引数で指定され、ヘルプで使用される説明は3番目の引数で指定されます。

制御したいコードで、DebugCounter::shouldExecuteを使用して制御します。

if (DebugCounter::shouldExecute(DeleteAnInstruction))
  I->eraseFromParent();

これだけです。これで、optを使用して、「--debug-counter」オプションを使用して、このコードがいつトリガーされるかを制御できます。コードパスをいつ実行するかを指定します。

$ opt --debug-counter=passname-delete-instruction=2-3 -passname

これにより、上記のコードは最初に2回スキップされ、次に2回実行され、残りの実行はスキップされます。

したがって、次のコードで実行された場合

%1 = add i32 %a, %b
%2 = add i32 %a, %b
%3 = add i32 %a, %b
%4 = add i32 %a, %b

番号%2%3が削除されます。

範囲引数の開始と終了をバイナリ検索するユーティリティがutils/bisect-skip-countで提供されています。デバッグカウンタ変数の範囲を自動的に最小化するために使用できます。

デバッグカウンタのチャンクリストを最小化するためのより一般的なユーティリティが、llvm/tools/reduce-chunk-list/reduce-chunk-list.cppに用意されています。

reduce-chunk-listの使用方法:まず、最小化したいデバッグカウンタへの呼び出し回数を把握します。これを行うには、最小化したいコンパイルコマンドを-print-debug-counter付きで実行し、必要に応じて-mllvmを追加します。次に、目的のカウンタを含む行を見つけます。次のようになります。

my-counter               : {5678,empty}

my-counterへの呼び出し回数は5678です。

次に、reduce-chunk-listを使用して、興味深い最小のチャンクセットを見つけます。次のようなリプロデューサースクリプトを作成します。

#! /bin/bash
opt -debug-counter=my-counter=$1
# ... Test result of the command. Failure of the script is considered interesting

次に、reduce-chunk-list my-script.sh 0-5678 2>&1 | tee dump_bisectを実行します。このコマンドには時間がかかる場合があります。ただし、完了すると、Minimal Chunks = 0:1:5:11-12:33-34のような結果が出力されます。

コードをデバッグ中にグラフを表示する

LLVMの重要なデータ構造のいくつかはグラフです。たとえば、LLVM BasicBlockから作成されたCFG、LLVM MachineBasicBlocksから作成されたCFG、およびInstruction Selection DAGsなどです。コンパイラのさまざまな部分をデバッグしているときに、これらのグラフを瞬時に視覚化できると便利な場合があります。

LLVMは、デバッグビルドで使用できるいくつかのコールバックを提供し、まさにそれを行います。たとえば、Function::viewCFG()メソッドを呼び出すと、現在のLLVMツールで、基本ブロックがグラフ内のノードであり、各ノードにブロック内の命令が含まれる関数のCFGを含むウィンドウが表示されます。同様に、Function::viewCFGOnly()(命令を含まない)、MachineFunction::viewCFG()MachineFunction::viewCFGOnly()、およびSelectionDAG::viewGraph()メソッドも存在します。たとえば、GDB内では、通常、call DAG.viewGraph()のようなものを使用してウィンドウを表示できます。または、デバッグしたい場所に、これらの関数への呼び出しをコードに散りばめることもできます。

これを動作させるには、少し設定が必要です。X11 を搭載した Unix システムでは、graphviz ツールキットをインストールし、 ‘dot’ と ‘gv’ がパスにあることを確認してください。 macOS で実行している場合は、macOS 版の Graphviz プログラム をダウンロードしてインストールし、/Applications/Graphviz.app/Contents/MacOS/ (またはインストールした場所)をパスに追加してください。プログラムは、設定、ビルド、LLVM の実行時には存在する必要はなく、アクティブなデバッグセッション中に必要に応じてインストールするだけで済みます。

SelectionDAG は、大規模で複雑なグラフ内の興味深いノードを簡単に見つけられるように拡張されました。 gdb から、call DAG.setGraphColor(node, "color") を呼び出すと、次の call DAG.viewGraph() で、指定した色でノードがハイライト表示されます(色の選択肢は colors で確認できます)。 より複雑なノード属性は、call DAG.setGraphAttrs(node, "attributes") で指定できます(選択肢は グラフ属性 で確認できます)。 すべての現在のグラフ属性を再開してクリアする場合は、call DAG.clearGraphAttrs() を呼び出すことができます。

グラフ視覚化機能は、ファイルサイズを削減するためにリリースビルドからコンパイルアウトされることに注意してください。つまり、これらの機能を使用するには、デバッグ + アサートまたはリリース + アサートビルドが必要です。

タスクに適したデータ構造を選択する

LLVM には、llvm/ADT/ ディレクトリに多数のデータ構造があり、一般的に STL データ構造を使用しています。 このセクションでは、選択する際に考慮すべきトレードオフについて説明します。

最初のステップは、自分で選択する冒険です。シーケンシャルコンテナ、セットのようなコンテナ、マップのようなコンテナのどれが必要ですか? コンテナを選択する際に最も重要なことは、コンテナへのアクセス方法のアルゴリズム的な特性です。 それに基づいて、以下を使用する必要があります。

  • ある値に基づいて別の値を効率的に検索する必要がある場合は、マップのようなコンテナ。 マップのようなコンテナは、包含(キーがマップ内にあるかどうか)の効率的なクエリもサポートしています。 マップのようなコンテナは、一般的に効率的な逆マッピング(値からキー)をサポートしていません。 それが必要な場合は、2 つのマップを使用します。 一部のマップのようなコンテナは、ソートされた順序でキーを効率的に反復処理することもサポートしています。 マップのようなコンテナは最もコストがかかるため、これらの機能のいずれかが必要な場合にのみ使用してください。

  • 重複を自動的に排除するコンテナに多数のものを入れる必要がある場合は、セットのようなコンテナ。 一部のセットのようなコンテナは、ソートされた順序で要素を効率的に反復処理することをサポートしています。 セットのようなコンテナは、シーケンシャルコンテナよりもコストがかかります。

  • 要素を追加する最も効率的な方法を提供し、コレクションに追加された順序を追跡する場合は、シーケンシャルコンテナ。 これらは重複を許可し、効率的な反復処理をサポートしますが、キーに基づいた効率的なルックアップはサポートしていません。

  • 文字またはバイト配列に使用される特殊なシーケンシャルコンテナまたは参照構造は、文字列コンテナです。

  • 数値 ID のセットに対してセット操作を格納および実行する効率的な方法を提供し、重複を自動的に排除するものは、ビットコンテナです。 ビットコンテナは、格納する ID ごとに最大 1 ビット必要です。

適切なカテゴリのコンテナが決定されたら、カテゴリのメンバーをインテリジェントに選択することで、メモリ使用量、定数係数、およびアクセス時のキャッシュ動作を微調整できます。 定数係数とキャッシュ動作は重要になる可能性があることに注意してください。 たとえば、通常は少数の要素しか含まない(ただし、多数含まれる可能性のある)ベクターがある場合は、vector よりも SmallVector を使用する方がはるかに優れています。 そうすることで、要素をコンテナに追加するコストをはるかに上回る、(比較的に) コストのかかる malloc/free 呼び出しを回避できます。

シーケンシャルコンテナ (std::vector、std::list など)

ニーズに応じて、さまざまなシーケンシャルコンテナを使用できます。 このセクションで、やりたいことを実行する最初のものを選択してください。

llvm/ADT/ArrayRef.h

llvm::ArrayRef クラスは、メモリ内の要素のシーケンシャルリストを受け取り、それらから読み取るだけのインターフェイスで使用するのに最適なクラスです。 ArrayRef を受け取ることで、API には、固定サイズの配列、std::vectorllvm::SmallVector、およびメモリ内で連続しているその他のものを渡すことができます。

固定サイズの配列

固定サイズの配列は非常にシンプルで非常に高速です。 含まれる要素の数を正確に把握している場合、または含まれる数の上限が (低い) 場合は適しています。

ヒープ割り当て配列

ヒープ割り当て配列 (new[] + delete[]) もシンプルです。 要素の数が可変である場合、配列が割り当てられる前に必要な要素の数を把握している場合、および配列が通常大きい場合は適しています (そうでない場合は、SmallVector を検討してください)。 ヒープ割り当て配列のコストは、new/delete (別名 malloc/free) のコストです。 また、コンストラクターを持つ型の配列を割り当てる場合、配列内のすべての要素に対してコンストラクターとデストラクターが実行されることにも注意してください (サイズ変更可能なベクターは、実際に使用される要素のみを構築します)。

llvm/ADT/TinyPtrVector.h

TinyPtrVector<Type> は、ベクターに 0 個または 1 個の要素がある場合に割り当てを回避するように最適化された、高度に特殊化されたコレクションクラスです。 これには 2 つの大きな制限があります。1) ポインター型の値のみを保持できること、2) null ポインターを保持できないことです。

このコンテナは高度に特殊化されているため、使用されることはめったにありません。

llvm/ADT/SmallVector.h

SmallVector<Type, N> は、vector<Type> と同じように見えるシンプルなクラスです。効率的な反復処理をサポートし、メモリ順に要素をレイアウトし(要素間でポインター演算を実行できます)、効率的な push_back/pop_back 操作をサポートし、要素への効率的なランダムアクセスなどをサポートします。

SmallVector の主な利点は、オブジェクト自体に、ある数の要素 (N) のスペースを割り当てることです。 このため、SmallVector が動的に N より小さい場合は、malloc は実行されません。 これは、malloc/free 呼び出しが要素をいじくるコードよりもはるかにコストがかかる場合に大きな利点になります。

これは「通常は小さい」ベクターに適しています (例: ブロックの前任者/後任者の数は通常 8 未満)。 一方、これにより SmallVector 自体のサイズが大きくなるため、大量に割り当てることは避けるべきです (そうすると、多くのスペースが無駄になります)。 そのため、SmallVector はスタック上で使用する場合に最も便利です。

インライン要素数 N の明確な動機付けのある選択がない場合は、SmallVector<T> を使用することをお勧めします (つまり、N を省略します)。 これにより、スタックへの割り当てに適したデフォルトのインライン要素数が選択されます (たとえば、sizeof(SmallVector<T>) を約 64 バイトに保つように試みます)。

SmallVector は、alloca の優れたポータブルで効率的な代替品も提供します。

SmallVector は、std::vector に対して他のいくつかの小さな利点を増やしており、SmallVector<Type, 0>std::vector<Type> よりも推奨されるようになりました。

  1. std::vector は例外安全であり、一部の実装では、SmallVector が要素を移動する場合に要素をコピーするという悲観的な最適化が行われます。

  2. SmallVector は std::is_trivially_copyable<Type> を理解し、realloc を積極的に使用します。

  3. 多くの LLVM API は、出力パラメーターとして SmallVectorImpl を受け取ります (以下の注を参照)。

  4. N が 0 の SmallVector は、サイズと容量に void* ではなく unsigned を使用するため、64 ビットプラットフォームでは std::vector より小さくなります。

パラメーター型として ArrayRef<T> または SmallVectorImpl<T> を使用することを優先してください。

パラメーター型として SmallVector<T, N> を使用することはめったに適切ではありません。 API がベクターから読み取るだけの場合は、ArrayRef を使用する必要があります。 API がベクターを更新する場合でも、「小さいサイズ」は関連性が低い可能性があります。 そのような API は、その後に割り当てられた要素を持たない「ベクターヘッダー」(およびメソッド) である SmallVectorImpl<T> クラスを使用する必要があります。 SmallVector<T, N>SmallVectorImpl<T> から継承するため、変換は暗黙的であり、コストはかかりません。 例:

// DISCOURAGED: Clients cannot pass e.g. raw arrays.
hardcodedContiguousStorage(const SmallVectorImpl<Foo> &In);
// ENCOURAGED: Clients can pass any contiguous storage of Foo.
allowsAnyContiguousStorage(ArrayRef<Foo> In);

void someFunc1() {
  Foo Vec[] = { /* ... */ };
  hardcodedContiguousStorage(Vec); // Error.
  allowsAnyContiguousStorage(Vec); // Works.
}

// DISCOURAGED: Clients cannot pass e.g. SmallVector<Foo, 8>.
hardcodedSmallSize(SmallVector<Foo, 2> &Out);
// ENCOURAGED: Clients can pass any SmallVector<Foo, N>.
allowsAnySmallSize(SmallVectorImpl<Foo> &Out);

void someFunc2() {
  SmallVector<Foo, 8> Vec;
  hardcodedSmallSize(Vec); // Error.
  allowsAnySmallSize(Vec); // Works.
}

名前には「Impl」が含まれていますが、SmallVectorImpl は広く使用されており、「実装のプライベート」ではなくなりました。 SmallVectorHeader のような名前の方が適切かもしれません。

llvm/ADT/PagedVector.h

PagedVector<Type, PageSize> は、operator[] を介してページの最初の要素にアクセスしたときに、型 TypePageSize 要素を割り当てるランダムアクセスコンテナーです。 これは、要素の数が事前にわかっていて、実際の初期化にコストがかかり、要素がまばらに使用される場合に役立ちます。 このユーティリティは、要素がアクセスされるときにページ単位の遅延初期化を使用します。 使用されるページ数が少ない場合は、大幅なメモリ節約を達成できます。

主な利点は、PagedVector が、ページが必要になるまで実際の割り当てを遅延できる点です。その代わり、ページごとにポインタが1つと、位置インデックスで要素にアクセスする際に間接参照が1つ増えます。

このコンテナのメモリフットプリントを最小限に抑えるためには、PageSize を小さすぎないように(そうしないと、ページごとのポインタのオーバーヘッドが大きくなりすぎる可能性がある)、また大きすぎないように(そうしないと、ページが完全に使用されない場合にメモリが浪費される)バランスを取ることが重要です。

さらに、ベクターのように挿入インデックスに基づいて要素の順序を保持しながら、begin() および end() による要素の反復処理は、API では提供されていません。これは、順番に要素にアクセスすると反復処理されるすべてのページが割り当てられ、メモリ節約と PagedVector の目的が損なわれるためです。

最後に、アクセスされたページに関連付けられた要素にアクセスするために、materialized_begin() および materialized_end イテレータが提供されます。これにより、初期化された要素を順不同で反復処理する必要がある操作を高速化できます。

<vector>

std::vector<T> は広く愛され、尊敬されています。しかし、上記の利点から、SmallVector<T, 0> の方が良い選択肢となることがよくあります。std::vector は、UINT32_MAX 個を超える要素を格納する必要がある場合や、ベクターを期待するコードとインターフェースする場合に役立ちます:)。

std::vector について1つ注意すべき点:次のようなコードは避けてください。

for ( ... ) {
   std::vector<foo> V;
   // make use of V.
}

代わりに、次のように記述してください。

std::vector<foo> V;
for ( ... ) {
   // make use of V.
   V.clear();
}

そうすることで、ループの反復処理ごとに(少なくとも)1つのヒープ割り当てと解放を節約できます。

<deque>

std::deque は、ある意味で std::vector の一般化されたバージョンです。std::vector と同様に、一定時間でのランダムアクセスやその他の同様の特性を提供しますが、リストの先頭への効率的なアクセスも提供します。メモリ内の要素の連続性は保証されません。

この余分な柔軟性と引き換えに、std::dequestd::vector よりも大幅に高い定数係数のコストがかかります。可能であれば、std::vector またはより安価なものを使用してください。

<list>

std::list は、非常に非効率的なクラスであり、ほとんど役に立ちません。挿入されるすべての要素に対してヒープ割り当てを実行するため、特に小さいデータ型の場合、定数係数が非常に高くなります。std::list は、双方向反復処理のみをサポートし、ランダムアクセス反復処理はサポートしていません。

この高コストと引き換えに、std::list はリストの両端(std::deque のように、ただし std::vectorSmallVector とは異なる)への効率的なアクセスをサポートします。さらに、std::list のイテレータ無効化特性は、ベクタークラスのイテレータ無効化特性よりも強力です。リストへの要素の挿入または削除は、リスト内の他の要素へのイテレータまたはポインタを無効化しません。

llvm/ADT/ilist.h

ilist<T> は「侵入型」双方向連結リストを実装します。侵入型であるのは、要素がリスト用の前後のポインタを格納し、アクセスを提供する必要があるためです。

ilist には std::list と同じ欠点があり、さらに要素型に ilist_traits の実装が必要ですが、いくつかの新しい特性を提供します。特に、ポリモーフィックオブジェクトを効率的に格納でき、要素がリストに挿入または削除されると traits クラスに通知され、ilist は一定時間のスプライス操作をサポートすることが保証されています。

ilistiplist は互いに using エイリアスであり、後者は現在、歴史的な理由でのみ存在します。

これらの特性は、Instruction や基本ブロックなど、まさに私たちが求めているものです。そのため、これらは ilist で実装されています。

関連する関心のあるクラスについては、次のサブセクションで説明します。

llvm/ADT/PackedVector.h

各値に少数のビットのみを使用して値のベクターを格納するのに便利です。ベクターのようなコンテナの標準操作とは別に、「or」セット操作も実行できます。

例:

enum State {
    None = 0x0,
    FirstCondition = 0x1,
    SecondCondition = 0x2,
    Both = 0x3
};

State get() {
    PackedVector<State, 2> Vec1;
    Vec1.push_back(FirstCondition);

    PackedVector<State, 2> Vec2;
    Vec2.push_back(SecondCondition);

    Vec1 |= Vec2;
    return Vec1[0]; // returns 'Both'.
}

ilist_traits

ilist_traits<T> は、ilist<T> のカスタマイズメカニズムです。ilist<T> は、この traits クラスからパブリックに派生します。

llvm/ADT/ilist_node.h

ilist_node<T> は、ilist<T> (および類似のコンテナ) によってデフォルトの方法で期待される前方リンクと後方リンクを実装します。

ilist_node<T> はノード型 T に埋め込まれることを想定しており、通常、Tilist_node<T> からパブリックに派生します。

番兵

ilist には、考慮する必要のある別の特殊性があります。C++ エコシステムで適切な構成要素となるためには、begin および end イテレータなどの標準コンテナ操作をサポートする必要があります。また、空でない ilist の場合、end イテレータで operator-- が正しく機能する必要があります。

この問題に対する唯一の妥当な解決策は、侵入型リストとともにいわゆる番兵を割り当てることです。これは end イテレータとして機能し、最後の要素へのバックリンクを提供します。ただし、C++ の規則に従って、番兵を超えて operator++ を実行することは違法であり、逆参照もしてはなりません。

これらの制約により、ilist は番兵の割り当てと格納方法についていくつかの実装上の自由度を持つことができます。対応するポリシーは、ilist_traits<T> によって規定されます。デフォルトでは、番兵が必要になるたびに T がヒープ割り当てされます。

デフォルトのポリシーはほとんどの場合十分ですが、T がデフォルトコンストラクタを提供しない場合は機能しなくなる可能性があります。また、ilist のインスタンスが多数ある場合、関連付けられた番兵のメモリオーバーヘッドは浪費されます。多数の大量の T-番兵による状況を軽減するために、場合によっては、ゴースト番兵につながるトリックが採用されることがあります。

ゴースト番兵は、メモリ内の ilist インスタンスで番兵を重ね合わせる、特別に作成された ilist_traits<T> によって取得されます。ポインタ演算を使用して番兵を取得します。これは、ilistthis ポインタを基準にしています。ilist は、番兵のバックリンクとして機能する追加のポインタによって拡張されます。これは、合法的にアクセスできるゴースト番兵の唯一のフィールドです。

その他のシーケンシャルコンテナオプション

std::string など、他の STL コンテナも利用できます。

std::queuestd::priority_queuestd::stack など、さまざまな STL アダプタクラスもあります。これらは、基になるコンテナへの簡略化されたアクセスを提供しますが、コンテナ自体のコストには影響しません。

文字列のようなコンテナ

C および C++ で文字列を渡して使用する方法はさまざまあり、LLVM は選択肢をいくつか追加しています。必要な処理を行うリストの最初のオプションを選択してください。相対コストに従って順序付けられています。

文字列を const char* として渡すことは、一般に推奨されません。これらには、埋め込みヌル(”0”)文字を表すことができないことや、長さを効率的に取得できないことなど、多くの問題があります。 'const char*' の一般的な代替は StringRef です。

API用の文字列コンテナの選択に関する詳細は、文字列の受け渡しを参照してください。

llvm/ADT/StringRef.h

StringRefクラスは、文字へのポインタと長さを格納する単純な値クラスであり、ArrayRefクラスと非常によく似ています(ただし、文字の配列に特化しています)。StringRefは長さを保持しているため、埋め込みのヌル文字を含む文字列を安全に処理でき、長さを取得するためにstrlen呼び出しは不要で、表現する文字範囲をスライスしたり細かく区切ったりするための非常に便利なAPIも備えています。

StringRefは、C文字列リテラル、std::string、C配列、またはSmallVectorであるなど、存続することがわかっている単純な文字列を渡すのに理想的です。これらの各ケースは、StringRefへの効率的な暗黙的な変換を持ち、その結果、動的なstrlenが実行されることはありません。

StringRefには、より強力な文字列コンテナが役立ついくつかの大きな制限があります。

  1. StringRefを直接「const char*」に変換することはできません。これは、(さまざまなより強力なクラスの.c_str()メソッドとは異なり)末尾のヌルを追加する方法がないためです。

  2. StringRefは、基になる文字列バイトを所有または存続させません。そのため、ダングリングポインタにつながりやすく、ほとんどの場合、データ構造に埋め込むのには適していません(代わりに、std::stringなどを使用してください)。

  3. 同じ理由で、メソッドが結果文字列を「計算」する場合、StringRefをメソッドの戻り値として使用することはできません。代わりに、std::stringを使用してください。

  4. StringRefでは、指し示された文字列バイトを変更したり、範囲からバイトを挿入または削除したりすることはできません。このような編集操作の場合、Twineクラスと相互運用します。

その強みと制限により、関数がStringRefを受け取り、オブジェクトのメソッドがそれが所有する文字列を指すStringRefを返すのはごく一般的です。

llvm/ADT/Twine.h

Twineクラスは、一連の連結でインラインで構築できる文字列を受け取るAPIの中間データ型として使用されます。Twineは、一時オブジェクトとしてスタック上にTwineデータ型の再帰インスタンス(単純な値オブジェクト)を形成し、それらをツリーにリンクし、Twineが消費されるときに線形化することで機能します。Twineは関数の引数としてのみ安全に使用でき、常にconst参照である必要があります。例:

void foo(const Twine &T);
...
StringRef X = ...
unsigned i = ...
foo(X + "." + Twine(i));

この例では、値を連結することで「blarg.42」のような文字列を形成し、「blarg」または「blarg.」を含む中間文字列を形成しません。

Twineはスタック上の一時オブジェクトで構築され、これらのインスタンスは現在のステートメントの最後に破棄されるため、本質的に危険なAPIです。たとえば、この単純なバリアントには未定義の動作が含まれており、おそらくクラッシュします。

void foo(const Twine &T);
...
StringRef X = ...
unsigned i = ...
const Twine &Tmp = X + "." + Twine(i);
foo(Tmp);

…一時変数が呼び出し前に破棄されるためです。とはいえ、Twineは中間std::stringの一時変数よりもはるかに効率的であり、StringRefとうまく連携します。ただし、その制限に注意してください。

llvm/ADT/SmallString.h

SmallStringは、StringRefを受け取る+=のような便利なAPIを追加するSmallVectorのサブクラスです。SmallStringは、事前に割り当てられたスペースにデータを保持するのに十分な場合、メモリの割り当てを回避し、必要な場合は一般的なヒープ割り当てにコールバックします。データを所有しているため、非常に安全に使用でき、文字列の完全な変更をサポートします。

SmallVectorと同様に、SmallStringの大きな欠点はsizeofです。小さい文字列に最適化されていますが、それ自体は特に小さくはありません。これは、スタック上の一時的なスクラッチバッファとしては非常にうまく機能しますが、一般にヒープに入れるべきではないことを意味します。頻繁に割り当てられるヒープデータ構造のメンバーとして、または値で返されるSmallStringを見ることは非常にまれです。

std::string

標準C++のstd::stringクラスは、(SmallStringのように)基になるデータを所有する非常に一般的なクラスです。sizeof(std::string)は非常に妥当であるため、ヒープデータ構造に埋め込んだり、値で返したりすることができます。一方、std::stringはインライン編集(たとえば、多くのものを連結するなど)には非常に非効率的であり、標準ライブラリによって提供されるため、そのパフォーマンス特性はホスト標準ライブラリ(たとえば、libc++とMSVCは高度に最適化された文字列クラスを提供し、GCCには非常に遅い実装が含まれています)に大きく依存します。

std::stringの主な欠点は、それらを大きくするほとんどすべての操作でメモリを割り当てることができ、これは遅くなることです。そのため、SmallVectorまたはTwineをスクラッチバッファとして使用し、結果を保持するにはstd::stringを使用する方が適切です。

セットのようなコンテナ (std::set, SmallSet, SetVector など)

セットのようなコンテナは、複数の値を単一の表現に正規化する必要がある場合に役立ちます。これを行う方法にはいくつかの異なる選択肢があり、さまざまなトレードオフが提供されます。

ソートされた「vector」

多くの要素を挿入してから、多くのクエリを実行する予定の場合は、std::sort+std::uniqueを使用して重複を削除するstd::vector(またはその他の順次コンテナ)を使用するのが最適な方法です。このアプローチは、使用パターンにこれら2つの異なるフェーズ(挿入とクエリ)がある場合に非常にうまく機能し、順次コンテナの適切な選択と組み合わせることができます。

この組み合わせは、いくつかの優れた特性を提供します。結果のデータはメモリ内で連続しており(キャッシュの局所性に適しています)、割り当てが少なく、アドレス指定が容易で(最終的なベクトルのイテレータは単なるインデックスまたはポインタです)、標準のバイナリ検索で効率的にクエリできます(たとえば、std::lower_bound; 等しい比較を行う要素の範囲全体が必要な場合は、std::equal_rangeを使用してください)。

llvm/ADT/SmallSet.h

通常は小さく、要素が比較的小さいセットのようなデータ構造がある場合は、SmallSet<Type, N>が適切な選択です。このセットには、N個の要素をインプレースで格納するスペースがあり(したがって、セットが動的にNより小さい場合、mallocトラフィックは不要です)、単純な線形検索でそれらにアクセスします。セットがN個の要素を超えて大きくなると、効率的なアクセスを保証するより高価な表現を割り当てます(ほとんどの型の場合、std::setにフォールバックしますが、ポインタの場合ははるかに優れたSmallPtrSetを使用します)。

このクラスの魔法は、小さいセットを非常に効率的に処理しますが、効率を失うことなく非常に大きなセットを正常に処理することです。

llvm/ADT/SmallPtrSet.h

SmallPtrSetには、SmallSetのすべての利点があり(ポインタのSmallSetSmallPtrSetで透過的に実装されます)、Nを超える挿入が実行された場合、単一の二次プローブハッシュテーブルが割り当てられ、必要に応じて拡張され、非常に効率的なアクセス(低い定数係数を持つ一定時間挿入/削除/クエリ)を提供し、mallocトラフィックには非常に倹約的です。

std::setとは異なり、挿入または削除が発生するたびにSmallPtrSetのイテレータは無効になることに注意してください。remove_ifメソッドを使用して、セットを反復処理しながら要素を削除できます。

また、イテレータによって訪問される値はソートされた順序で訪問されません。

llvm/ADT/StringSet.h

StringSetStringMap<char>の薄いラッパーであり、一意の文字列の効率的な格納と取得を可能にします。

機能的にはSmallSet<StringRef>に類似しており、StringSetも反復処理をサポートしています。(イテレータはStringMapEntry<char>に逆参照するため、StringSetの項目にアクセスするにはi->getKey()を呼び出す必要があります。)一方、StringSetは範囲挿入とコピーコンストラクションをサポートしていませんが、SmallSetSmallPtrSetはサポートしています。

llvm/ADT/DenseSet.h

DenseSetは、単純な二次プローブハッシュテーブルです。小さい値をサポートすることに優れています。セットに現在挿入されているすべてのペアを保持するために単一の割り当てを使用します。DenseSetは、単純なポインタではない小さい値を一意にするための優れた方法です(ポインタにはSmallPtrSetを使用してください)。DenseSetには、DenseMapと同じ値型の要件があることに注意してください。

llvm/ADT/SparseSet.h

SparseSetは、中程度のサイズの符号なしキーで識別される少数のオブジェクトを保持します。多くのメモリを使用しますが、ベクターとほぼ同じ速度で動作を提供します。一般的なキーは、物理レジスタ、仮想レジスタ、または番号付きの基本ブロックです。

SparseSetは、非常に高速なクリア/検索/挿入/削除と、小さなセットに対する高速なイテレーションを必要とするアルゴリズムに役立ちます。複合データ構造を構築するためのものではありません。

llvm/ADT/SparseMultiSet.h

SparseMultiSetは、SparseSetの望ましい属性を保持しながら、SparseSetにマルチセットの動作を追加します。SparseSetと同様に、通常は多くのメモリを使用しますが、ベクトルとほぼ同じくらい高速な操作を提供します。典型的なキーは、物理レジスタ、仮想レジスタ、または番号付きの基本ブロックです。

SparseMultiSetは、コレクション全体の非常に高速なクリア/検索/挿入/削除、およびキーを共有する要素のセットに対するイテレーションを必要とするアルゴリズムに役立ちます。複合データ構造(例えば、ベクトルのベクトル、ベクトルのマップ)を使用するよりも効率的な選択肢となることが多いです。複合データ構造を構築するためのものではありません。

llvm/ADT/FoldingSet.h

FoldingSetは、作成にコストがかかるオブジェクトや多態的なオブジェクトの一意化に非常に優れた集合クラスです。これは、連鎖ハッシュテーブルと侵入型リンク(一意化されたオブジェクトはFoldingSetNodeから継承する必要がある)の組み合わせであり、IDプロセスの一部としてSmallVectorを使用します。

複雑なオブジェクト(例えば、コードジェネレータのノード)に対して「getOrCreateFoo」メソッドを実装したい場合を考えてみましょう。クライアントは、生成したいもの(オペコードとすべてのオペランドを知っている)の説明を持っていますが、ノードを「new」して、それが既に存在することを確認するためにセットに挿入を試みることは避けたいです。その場合、ノードを削除して、既に存在するノードを返す必要があります。

このスタイルのクライアントをサポートするために、FoldingSetは、クエリ対象の要素を記述するために使用できるFoldingSetNodeID(SmallVectorをラップします)を使用してクエリを実行します。クエリは、IDに一致する要素を返すか、挿入がどこで行われるべきかを示す不透明なIDを返します。IDの構築は通常、ヒープトラフィックを必要としません。

FoldingSetは侵入型リンクを使用するため、セット内の多態的なオブジェクトをサポートできます(例えば、SDNodeインスタンスをLoadSDNodesと混在させることができます)。要素は個別に割り当てられるため、要素へのポインタは安定しています。要素の挿入または削除によって、他の要素へのポインタが無効になることはありません。

<set>

std::setは、多くの点で優れているが、特に優れている点はない、妥当な万能セットクラスです。std::setは、挿入された各要素に対してメモリを割り当て(したがって、非常にmalloc集中型です)、通常はセット内の要素ごとに3つのポインタを格納します(したがって、要素ごとのスペースオーバーヘッドが大きくなります)。log(n)のパフォーマンスが保証されていますが、これは複雑さの観点から特に高速ではなく(特に、セットの要素が文字列のように比較にコストがかかる場合)、検索、挿入、および削除の定数係数が非常に高くなっています。

std::setの利点は、そのイテレータが安定していること(セットからの要素の削除または挿入は、他の要素へのイテレータまたはポインタに影響を与えない)と、セットに対するイテレーションがソートされた順序であることが保証されていることです。セット内の要素が大きい場合、ポインタとmallocトラフィックの相対的なオーバーヘッドは大きな問題ではありませんが、セットの要素が小さい場合、std::setはほとんどの場合、適切な選択肢ではありません。

llvm/ADT/SetVector.h

LLVMのSetVector<Type>は、選択したセットのようなコンテナと順次コンテナを組み合わせたアダプタクラスです。これが提供する重要な特性は、イテレーションサポートによる一意化(重複要素は無視される)による効率的な挿入です。これは、セットのようなコンテナと順次コンテナの両方に要素を挿入し、一意化にはセットのようなコンテナを、イテレーションには順次コンテナを使用することで実装されます。

SetVectorと他のセットの違いは、イテレーションの順序がSetVectorへの挿入順序と一致することが保証されていることです。この特性は、ポインタのセットなどの場合に非常に重要です。ポインタ値は非決定的であるため(例えば、異なるマシンでのプログラムの実行によって異なります)、セット内のポインタに対するイテレーションは、明確に定義された順序にはなりません。

SetVectorの欠点は、通常のセットの2倍のスペースを必要とし、使用するセットのようなコンテナと順次コンテナからの定数係数の合計があることです。決定的な順序で要素をイテレートする必要がある場合のみ使用してください。SetVectorは、要素を削除するのにコストがかかります(線形時間)。ただし、より高速な「pop_back」メソッドを使用する場合は除きます。

SetVectorは、デフォルトで、基礎となるコンテナにstd::vectorとサイズ16のSmallSetを使用するアダプタクラスであるため、非常にコストがかかります。ただし、"llvm/ADT/SetVector.h"は、指定されたサイズのSmallVectorSmallSetの使用をデフォルトとするSmallSetVectorクラスも提供します。これを使用し、セットが動的にNよりも小さい場合、ヒープトラフィックを大幅に節約できます。

llvm/ADT/UniqueVector.h

UniqueVectorはSetVectorに似ていますが、セットに挿入された各要素に対して一意のIDを保持します。内部にはマップとベクトルが含まれており、セットに挿入された各値に対して一意のIDを割り当てます。

UniqueVectorは非常にコストがかかります。コストは、マップとベクトルの両方を維持するコストの合計であり、複雑さが高く、定数係数が高く、多くのmallocトラフィックを生成します。避けるべきです。

llvm/ADT/ImmutableSet.h

ImmutableSetは、AVL木に基づいた不変(関数型)のセット実装です。要素の追加または削除は、Factoryオブジェクトを介して行われ、新しいImmutableSetオブジェクトの作成につながります。特定のコンテンツを持つImmutableSetが既に存在する場合、既存のものが返されます。等価性はFoldingSetNodeIDと比較されます。追加または削除操作の時間および空間複雑度は、元のセットのサイズの対数です。

セットの要素を返すメソッドはなく、メンバシップを確認することしかできません。

その他のセットのようなコンテナのオプション

STLは、std::multisetやstd::unordered_setなど、他のいくつかのオプションを提供しています。unordered_setのようなコンテナは、一般的に非常にコストがかかるため(各挿入にはmallocが必要)、使用することはありません。

std::multisetは、重複の除去に関心がない場合に役立ちますが、std::setのすべての欠点があります。ソートされたベクトル(重複エントリを削除しない)または他の何らかのアプローチがほとんどの場合、優れています。

マップのようなコンテナ(std::map、DenseMapなど)

マップのようなコンテナは、データをキーに関連付けたい場合に役立ちます。いつものように、これを行う方法はたくさんあります。:)

ソートされた「ベクトル」

使用パターンが厳密な挿入-クエリのアプローチに従う場合、セットのようなコンテナのソートされたベクトルと同じアプローチを簡単に使用できます。唯一の違いは、クエリ関数(効率的なlog(n)ルックアップのためにstd::lower_boundを使用します)が、キーと値の両方ではなく、キーのみを比較する必要があることです。これにより、セットのソートされたベクトルと同じ利点が得られます。

llvm/ADT/StringMap.h

文字列は、マップのキーとして一般的に使用されており、効率的にサポートするのが困難です。可変長であり、長い場合はハッシュと比較が非効率的であり、コピーするのにコストがかかるなどです。StringMapは、これらの問題に対処するように設計された特殊なコンテナです。任意のバイト範囲を、任意の別のオブジェクトにマッピングすることをサポートします。

StringMapの実装では、二次的にプローブされたハッシュテーブルを使用します。バケットには、ヒープに割り当てられたエントリへのポインタ(およびその他のもの)が格納されます。マップ内のエントリは、文字列が可変長であるため、ヒープに割り当てる必要があります。文字列データ(キー)と要素オブジェクト(値)は、要素オブジェクトの直後に文字列データがある同じ割り当てに格納されます。このコンテナは、値に対して「(char*)(&Value+1)」がキー文字列を指すことを保証します。

StringMapは、いくつかの理由で非常に高速です。二次プロービングはルックアップに対して非常にキャッシュ効率が高く、バケット内の文字列のハッシュ値は要素のルックアップ時に再計算されず、StringMapは値のルックアップ時に(ハッシュ衝突が発生した場合でも)無関係なオブジェクトのメモリに触れる必要がほとんどなく、ハッシュテーブルの成長はテーブルに既にある文字列のハッシュ値を再計算せず、マップ内の各ペアは単一の割り当てに格納されます(文字列データはペアの値と同じ割り当てに格納されます)。

StringMapは、バイト範囲を取得するクエリメソッドも提供しているため、値がテーブルに挿入された場合にのみ文字列をコピーします。

ただし、StringMapのイテレーション順序は決定的であることが保証されていないため、それを必要とするあらゆる用途では、代わりにstd::mapを使用する必要があります。

llvm/ADT/IndexedMap.h

IndexedMapは、小さな密な整数(または小さな密な整数にマッピングできる値)を別の型にマッピングするための特殊なコンテナです。内部的には、キーを密な整数範囲にマッピングするマッピング関数を持つベクトルとして実装されます。

これは、LLVMコードジェネレータの仮想レジスタのような場合に役立ちます。仮想レジスタは、コンパイル時定数(最初の仮想レジスタID)によってオフセットされる密なマッピングを持っています。

llvm/ADT/DenseMap.h

DenseMapは、単純な二次的にプローブされたハッシュテーブルです。小さいキーと値をサポートすることに優れています。マップに現在挿入されているすべてのペアを保持するために、単一の割り当てを使用します。DenseMapは、ポインタをポインタにマッピングしたり、他の小さい型を相互にマッピングするのに最適な方法です。

ただし、DenseMapには注意すべき点がいくつかあります。mapとは異なり、DenseMapのイテレータは挿入が発生するたびに無効になります。また、DenseMapは多数のキー/値ペア用のスペースを割り当てるため(デフォルトでは64から開始)、キーまたは値が大きい場合は多くのスペースを浪費します。最後に、キーがまだサポートされていない場合は、必要なキーのDenseMapInfoの部分的な特殊化を実装する必要があります。これは、内部的に必要な2つの特別なマーカー値(マップに挿入できない)をDenseMapに伝えるために必要です。

DenseMap の find_as() メソッドは、代替キー型を使用した検索操作をサポートします。これは、通常のキー型を構築するのにコストがかかるが、比較は安価な場合に役立ちます。DenseMapInfo は、使用される各代替キー型に対して適切な比較およびハッシュメソッドを定義する役割を担います。

DenseMap.h には、SmallVector と同様に、テンプレートパラメータ N の要素数を超えるまでヒープ割り当てを行わない SmallDenseMap バリアントも含まれています。

llvm/IR/ValueMap.h

ValueMap は、DenseMap をラップし、Value* (またはサブクラス) を別の型にマッピングします。Value が削除されたり RAUW されたりすると、ValueMap は自身を更新し、キーの新しいバージョンが同じ値にマッピングされるようにします。まるでキーが WeakVH であるかのようにです。ValueMap テンプレートに Config パラメータを渡すことで、この動作と、これらの 2 つのイベントで他に何が起こるかを正確に設定できます。

llvm/ADT/IntervalMap.h

IntervalMap は、小さなキーと値のためのコンパクトなマップです。これは単一のキーではなくキー間隔をマップし、隣接する間隔を自動的に結合します。マップに少数の間隔しか含まれていない場合、割り当てを避けるために、それらはマップオブジェクト自体に格納されます。

IntervalMap のイテレータは非常に大きいため、STL イテレータとして渡すべきではありません。ヘビーウェイトイテレータにより、より小さなデータ構造が可能になります。

llvm/ADT/IntervalTree.h

llvm::IntervalTree は、間隔を保持するための軽量なツリーデータ構造です。これにより、指定された任意のポイントとオーバーラップするすべての間隔を見つけることができます。現時点では、削除またはリバランス操作はサポートされていません。

IntervalTree は、一度セットアップしたら、それ以上の追加なしにクエリを実行するように設計されています。

<map>

std::map は、std::set と同様の特性を持っています。マップに挿入されたペアごとに 1 回の割り当てを使用し、非常に大きな定数係数で log(n) の検索を提供し、マップ内のペアごとに 3 つのポインタの空間ペナルティを課すなどです。

std::map は、キーまたは値が非常に大きい場合、ソートされた順序でコレクションを反復処理する必要がある場合、またはマップへの安定したイテレータ(つまり、別の要素の挿入または削除が発生した場合に無効にならないイテレータ)が必要な場合に最も役立ちます。

llvm/ADT/MapVector.h

MapVector<KeyT,ValueT> は、DenseMap インターフェースのサブセットを提供します。主な違いは、イテレーションの順序が挿入順序であることが保証されているため、ポインタのマップの非決定的なイテレーションのための簡単(ただし、ややコストのかかる)ソリューションになることです。

これは、キーからキーと値のペアのベクトルのインデックスにマッピングすることによって実装されます。これにより、高速なルックアップとイテレーションが提供されますが、2 つの主な欠点があります。キーが 2 回格納され、要素の削除には線形時間がかかります。要素を削除する必要がある場合は、remove_if() を使用して一括で削除するのが最善です。

llvm/ADT/IntEqClasses.h

IntEqClasses は、小さな整数の同値クラスのコンパクトな表現を提供します。最初は、範囲 0..n-1 の各整数は独自の同値クラスを持ちます。クラスは、2 つのクラス代表を join(a, b) メソッドに渡すことで結合できます。findLeader() が同じ代表を返す場合、2 つの整数は同じクラスにあります。

すべての同値クラスが形成されると、マップを圧縮して、各整数 0..n-1 が範囲 0..m-1 の同値クラス番号にマップできます。ここで、m は同値クラスの合計数です。マップは、再び編集できるようになる前に解凍する必要があります。

llvm/ADT/ImmutableMap.h

ImmutableMap は、AVL ツリーに基づく不変(関数型)マップの実装です。要素の追加または削除は Factory オブジェクトを介して行われ、新しい ImmutableMap オブジェクトの作成につながります。指定されたキーセットで ImmutableMap が既に存在する場合、既存のものが返されます。等価性は FoldingSetNodeID で比較されます。追加または削除操作の時間および空間計算量は、元のマップのサイズに対して対数です。

その他のマップのようなコンテナのオプション

STL は、std::multimap や std::unordered_map など、いくつかの他のオプションを提供しています。unordered_map のようなコンテナは、一般的に非常にコストがかかる(各挿入には malloc が必要)ため、使用しません。

std::multimap は、キーを複数の値にマップしたい場合に役立ちますが、std::map のすべての欠点があります。ソートされたベクトルまたはその他のアプローチがほぼ常に優れています。

ビットストレージコンテナ

ビットストレージコンテナはいくつかあり、それぞれをいつ使用するかを選択するのは比較的簡単です。

追加のオプションの 1 つは std::vector<bool> です。次の 2 つの理由から、その使用は推奨されません。1) 多くの一般的なコンパイラ (例: 一般的に入手可能な GCC のバージョン) での実装は非常に非効率的であり、2) C++ 標準委員会はこのコンテナを非推奨にするか、何らかの形で大幅に変更する可能性があります。いずれにせよ、使用しないでください。

BitVector

BitVector コンテナは、操作のための動的なサイズのビットセットを提供します。個々のビットの設定/テスト、およびセット操作をサポートします。セット操作には O(ビットベクトルのサイズ) の時間がかかりますが、操作は一度に 1 ビットではなく、一度に 1 ワードで実行されます。これにより、BitVector は他のコンテナと比較して、セット操作で非常に高速になります。設定されたビット数が多くなる (つまり、密なセット) と予想される場合は、BitVector を使用します。

SmallBitVector

SmallBitVector コンテナは、BitVector と同じインターフェースを提供しますが、必要なビット数が 25 程度と少ない場合に最適化されています。また、より大きなビット数も透過的にサポートしますが、プレーンな BitVector よりもわずかに効率が劣るため、より大きな数がまれな場合にのみ SmallBitVector を使用する必要があります。

現時点では、SmallBitVector はセット操作(and、or、xor)をサポートしておらず、その operator[] は割り当て可能な lvalue を提供しません。

SparseBitVector

SparseBitVector コンテナは BitVector によく似ていますが、1 つの大きな違いがあります。設定されているビットのみが格納されます。これにより、セットがスパースな場合、SparseBitVector は BitVector よりもはるかに省スペースになり、セット操作は O(設定されたビット数) になり、O(ユニバースのサイズ) ではなくなります。SparseBitVector の欠点は、ランダムビットの設定とテストが O(N) であり、大きな SparseBitVector では、BitVector よりも遅くなる可能性があることです。私たちの実装では、ソートされた順序(順方向または逆方向)でのビットの設定またはテストは、最悪の場合 O(1) です。現在のビットから 128 ビット以内(サイズに依存)のビットのテストと設定も O(1) です。一般的に言えば、SparseBitVector でのビットのテスト/設定は、最後に設定されたビットからの距離の O(1) です。

CoalescingBitVector

CoalescingBitVector コンテナは、原理的には SparseBitVector に似ていますが、設定されたビットの大きな連続範囲をコンパクトに表現するように最適化されています。これは、設定されたビットの連続範囲を間隔に結合することによって行われます。CoalescingBitVector でのビットの検索は O(連続範囲間のギャップのログ) です。

CoalescingBitVector は、設定されたビットの範囲間のギャップが大きい場合に、BitVector よりも優れた選択肢です。find() 操作が高速で予測可能なパフォーマンスを持つ必要がある場合は、SparseBitVector よりも優れた選択肢です。ただし、非常に短い範囲がたくさんあるセットを表現するのに適した選択肢ではありません。例: セット {2*x : x in [0, n)} は病的な入力になります。

便利なユーティリティ関数

LLVM は、コードベース全体で使用される多数の汎用ユーティリティ関数を実装しています。最も一般的なものは STLExtras.h (doxygen) にあります。これらのいくつかは、よく知られた C++ 標準ライブラリ関数をラップし、その他は LLVM に固有のものです。

範囲の反復処理

一度に複数の範囲を反復処理したり、インデックスのインデックスを知りたい場合があります。LLVM は、すべてのイテレータやインデックスを手動で管理しなくても、これを簡単にするカスタムユーティリティ関数を提供します。

zip* 関数

zip* 関数を使用すると、2 つ以上の範囲の要素を同時に反復処理できます。例えば

SmallVector<size_t> Counts = ...;
char Letters[26] = ...;
for (auto [Letter, Count] : zip_equal(Letters, Counts))
  errs() << Letter << ": " << Count << "\n";

要素は「参照ラッパー」プロキシ型 (参照のタプル) を介して提供され、構造化バインディング宣言と組み合わせることで、LetterCount が範囲要素への参照になります。Letters または Counts の要素に対するこれらの参照への変更は、要素に影響します。

zip* 関数は、次のような一時的な範囲をサポートします

for (auto [Letter, Count] : zip(SmallVector<char>{'a', 'b', 'c'}, Counts))
  errs() << Letter << ": " << Count << "\n";

zip ファミリの関数の違いは、指定された範囲の長さが異なる場合の動作です。

  • zip_equal – すべての入力範囲が同じ長さであることを要求します。

  • zip – 最も短い範囲の終わりに達すると、イテレーションが停止します。

  • zip_first – 最初の範囲が最も短いものであることを要求します。

  • zip_longest – 最も長い範囲の終わりに達するまで、イテレーションが続きます。より短い範囲の存在しない要素は、std::nullopt で置き換えられます。

長さの要件は、assert でチェックされます。

経験則として、すべての範囲が同じ長さになることを期待する場合は zip_equal を使用することを優先し、そうでない場合にのみ代替の zip 関数を検討してください。これは、zip_equal がこの同じ長さの前提を明確に伝え、(リリースモードで)最高の実行時パフォーマンスを持つためです。

enumerate

enumerate 関数を使用すると、現在のループ反復のインデックスを追跡しながら、1つ以上の範囲を反復処理できます。例:

for (auto [Idx, BB, Value] : enumerate(Phi->blocks(),
                                       Phi->incoming_values()))
  errs() << "#" << Idx << " " << BB->getName() << ": " << *Value << "\n";

現在の要素インデックスは、最初の構造化束縛要素として提供されます。あるいは、インデックスと要素の値は、index() および value() メンバー関数で取得できます。

char Letters[26] = ...;
for (auto En : enumerate(Letters))
  errs() << "#" << En.index() << " " << En.value() << "\n";

enumeratezip_equal のセマンティクスを持ち、「参照ラッパー」プロキシを介して要素を提供することに注意してください。これにより、構造化束縛または value() メンバー関数を介してアクセスすると、要素が変更可能になります。2つ以上の範囲が渡される場合、enumerate はそれらが同じ長さであることを要求します(assert でチェックされます)。

デバッグ

いくつかのコアLLVMライブラリ用に、いくつかの GDBプリティプリンター が提供されています。これらを使用するには、以下を実行します(または、~/.gdbinit に追加します)。

source /path/to/llvm/src/utils/gdb-scripts/prettyprinters.py

データ構造が大きなテキストブロックとして出力されるのを避けるために、print pretty オプションを有効にすると便利な場合もあります。

一般的な操作に関する役立つヒント

このセクションでは、LLVMコードの非常に単純ないくつかの変換を実行する方法について説明します。これは、LLVM変換の実用的な側面を示す、一般的なイディオムの例を示すことを目的としています。

これは「ハウツー」セクションであるため、作業対象となる主要なクラスについても読む必要があります。コアLLVMクラス階層リファレンス には、知っておくべき主要なクラスの詳細と説明が含まれています。

基本的な検査およびトラバーサルルーチン

LLVMコンパイラーインフラストラクチャには、トラバースできる多くの異なるデータ構造があります。C++標準テンプレートライブラリの例に従って、これらのさまざまなデータ構造をトラバースするために使用される手法は、基本的にすべて同じです。列挙可能な値のシーケンスの場合、XXXbegin() 関数(またはメソッド)はシーケンスの先頭へのイテレーターを返し、XXXend() 関数はシーケンスの最後の有効な要素の1つ後ろを指すイテレーターを返し、2つの操作間で共通の XXXiterator データ型があります。

反復のパターンはプログラム表現のさまざまな側面で共通であるため、標準テンプレートライブラリのアルゴリズムをそれらに使用でき、反復方法を覚えやすくなっています。まず、トラバースする必要があるデータ構造のいくつかの一般的な例を示します。他のデータ構造は非常に似た方法でトラバースされます。

Function 内の BasicBlock の反復処理

何らかの方法で変換したい Function インスタンスを持つことはごく一般的です。特に、その BasicBlock を操作したいと思うでしょう。これを容易にするために、Function を構成するすべての BasicBlock を反復処理する必要があります。以下は、BasicBlock の名前と、それに含まれる Instruction の数を出力する例です。

Function &Func = ...
for (BasicBlock &BB : Func)
  // Print out the name of the basic block if it has one, and then the
  // number of instructions that it contains
  errs() << "Basic block (name=" << BB.getName() << ") has "
             << BB.size() << " instructions.\n";

BasicBlock 内の Instruction の反復処理

Function 内の BasicBlock を扱う場合と同様に、BasicBlock を構成する個々の命令を反復処理するのは簡単です。次に、BasicBlock 内の各命令を出力するコードスニペットを示します。

BasicBlock& BB = ...
for (Instruction &I : BB)
   // The next statement works since operator<<(ostream&,...)
   // is overloaded for Instruction&
   errs() << I << "\n";

ただし、これは BasicBlock の内容を出力する最適な方法ではありません。ostream演算子は、事実上、気に掛けるすべてのものに対してオーバーロードされているため、基本ブロック自体で出力ルーチンを呼び出すことができます。errs() << BB << "\n";

Function 内の Instruction の反復処理

FunctionBasicBlock を反復処理し、その BasicBlockInstruction を反復処理することがよくあることがわかった場合は、代わりに InstIterator を使用する必要があります。llvm/IR/InstIterator.h (doxygen) を含め、コード内で InstIterator を明示的にインスタンス化する必要があります。次に、関数のすべての命令を標準エラーストリームに出力する方法を示す簡単な例を示します。

#include "llvm/IR/InstIterator.h"

// F is a pointer to a Function instance
for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I)
  errs() << *I << "\n";

簡単ですよね。また、InstIterator を使用して、ワークリストを初期コンテンツで埋めることもできます。たとえば、ワークリストを初期化して Function F のすべての命令を含める場合は、次のようにします。

std::set<Instruction*> worklist;
// or better yet, SmallPtrSet<Instruction*, 64> worklist;

for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I)
  worklist.insert(&*I);

これで、STLセット worklist には、F で示される Function 内のすべての命令が含まれるようになります。

イテレータをクラスポインタに(およびその逆に)変換する

手元にあるのがイテレーターだけの場合、クラスインスタンスへの参照(またはポインター)を取得すると便利な場合があります。イテレーターから参照またはポインターを抽出するのは非常に簡単です。iBasicBlock::iterator で、jBasicBlock::const_iterator であると仮定すると

Instruction& inst = *i;   // Grab reference to instruction reference
Instruction* pinst = &*i; // Grab pointer to instruction reference
const Instruction& inst = *j;

クラスポインタを対応するイテレーターに変換することも可能であり、これは一定時間の操作です(非常に効率的)。次のコードスニペットは、LLVMイテレーターによって提供される変換コンストラクターの使用法を示しています。これらを使用すると、何らかの構造体の反復処理を介して実際に取得することなく、明示的に何かのイテレーターを取得できます。

void printNextInstruction(Instruction* inst) {
  BasicBlock::iterator it(inst);
  ++it; // After this line, it refers to the instruction after *inst
  if (it != inst->getParent()->end()) errs() << *it << "\n";
}

呼び出しサイトの検索:少し複雑な例

FunctionPass を作成していて、特定の関数 (つまり、いくつかの Function *) がすでにスコープ内にあるモジュール全体 (つまり、すべての Function 全体) のすべての場所をカウントしたいとします。後で学習するように、InstVisitor を使用すると、より簡単な方法でこれを実現できますが、この例では、InstVisitor がない場合にどのように行うかを探求することができます。擬似コードでは、これが実行したいことです。

initialize callCounter to zero
for each Function f in the Module
  for each BasicBlock b in f
    for each Instruction i in b
      if (i a Call and calls the given function)
        increment callCounter

実際のコードは次のとおりです(FunctionPass を作成しているため、FunctionPass 派生クラスは runOnFunction メソッドをオーバーライドするだけで済みます)。

Function* targetFunc = ...;

class OurFunctionPass : public FunctionPass {
  public:
    OurFunctionPass(): callCounter(0) { }

    virtual runOnFunction(Function& F) {
      for (BasicBlock &B : F) {
        for (Instruction &I: B) {
          if (auto *CB = dyn_cast<CallBase>(&I)) {
            // We know we've encountered some kind of call instruction (call,
            // invoke, or callbr), so we need to determine if it's a call to
            // the function pointed to by m_func or not.
            if (CB->getCalledFunction() == targetFunc)
              ++callCounter;
          }
        }
      }
    }

  private:
    unsigned callCounter;
};

def-use および use-def チェーンの反復処理

多くの場合、Value クラス (doxygen) のインスタンスがあり、どの UserValue を使用しているかを判断したい場合があります。特定の Value のすべての User のリストは、def-use チェーンと呼ばれます。たとえば、特定の関数 foo への Function* という名前の F があるとします。foo を *使用* するすべての命令を見つけるのは、F の *def-use* チェーンを反復処理するのと同じくらい簡単です。

Function *F = ...;

for (User *U : F->users()) {
  if (Instruction *Inst = dyn_cast<Instruction>(U)) {
    errs() << "F is used in instruction:\n";
    errs() << *Inst << "\n";
  }

別の方法として、User クラス(doxygen)のインスタンスがあり、それによって使用されている Value を知る必要があるのが一般的です。User によって使用されるすべての Value のリストは、use-def チェーンとして知られています。Instruction クラスのインスタンスは一般的な User であるため、特定の命令が使用するすべての値(つまり、特定の Instruction のオペランド)を反復処理したい場合があります。

Instruction *pi = ...;

for (Use &U : pi->operands()) {
  Value *v = U.get();
  // ...
}

オブジェクトを const として宣言することは、(分析などの)ミューテーションフリーのアルゴリズムを強制するための重要なツールです。この目的のために、上記イテレータは、Value::const_use_iterator および Value::const_op_iterator として定数フレーバーで提供されます。const Value* または const User*use/op_begin() を呼び出すと、これらが自動的に発生します。逆参照すると、const Use* を返します。それ以外の場合、上記のパターンは変わりません。

ブロックの前任者と後任者の反復処理

"llvm/IR/CFG.h" で定義されたルーチンを使用すると、ブロックの前任者と後任者を反復処理するのは非常に簡単です。BB のすべての前任者を反復処理するには、次のようなコードを使用します。

#include "llvm/IR/CFG.h"
BasicBlock *BB = ...;

for (BasicBlock *Pred : predecessors(BB)) {
  // ...
}

同様に、後任者を反復処理するには successors を使用します。

簡単な変更を加える

LLVMインフラストラクチャには、知っておくべきいくつかのプリミティブな変換操作があります。変換を実行するときは、基本ブロックの内容を操作するのがごく一般的です。このセクションでは、それを行うための一般的な方法のいくつかについて説明し、コード例を示します。

新しい Instruction の作成と挿入

命令のインスタンス化

Instruction の作成は簡単です。インスタンス化する命令の種類に対してコンストラクタを呼び出し、必要なパラメータを指定するだけです。たとえば、AllocaInst は (const-ptr-to) Type要求するだけです。したがって、

auto *ai = new AllocaInst(Type::Int32Ty);

現在のスタックフレームで実行時に1つの整数を割り当てることを表す AllocaInst インスタンスが作成されます。Instruction の各サブクラスには、命令のセマンティクスを変更するさまざまなデフォルトパラメータがある可能性が高いため、インスタンス化に関心のあるInstruction のサブクラスの doxygen ドキュメントを参照してください。

値の命名

変換のデバッグを容易にするため、可能な場合は命令の値に名前を付けると非常に便利です。生成されたLLVMマシンコードを調べる場合は、命令の結果に関連付けられた論理名が必ず必要になります!Instruction コンストラクタの Name (デフォルト) パラメータに値を指定すると、実行時の命令の実行結果に論理名を関連付けます。たとえば、スタック上に整数のためのスペースを動的に割り当てる変換を作成していて、その整数が他のコードによって何らかのインデックスとして使用されるとします。これを実現するために、Function の最初の BasicBlock の最初のポイントに AllocaInst を配置し、同じ Function 内で使用するつもりです。次のように行うことができます。

auto *pa = new AllocaInst(Type::Int32Ty, 0, "indexLoc");

ここで、indexLoc は、実行時スタック上の整数へのポインタである命令の実行値の論理名です。

命令の挿入

BasicBlock を形成する既存の命令シーケンスに Instruction を挿入するには、基本的に3つの方法があります。

  • BasicBlock の命令リストへの挿入

    BasicBlock* pb、その BasicBlock 内の Instruction* pi、および *pi の前に挿入する新しく作成された命令が与えられた場合、次のようにします。

    BasicBlock *pb = ...;
    Instruction *pi = ...;
    auto *newInst = new Instruction(...);
    
    newInst->insertBefore(pi); // Inserts newInst before pi
    

    BasicBlock の末尾への追加は非常に一般的なため、Instruction クラスと Instruction から派生したクラスには、追加先の BasicBlock へのポインタを取るコンストラクタが用意されています。たとえば、次のようなコードの場合

    BasicBlock *pb = ...;
    auto *newInst = new Instruction(...);
    
    newInst->insertInto(pb, pb->end()); // Appends newInst to pb
    

    次のようになります。

    BasicBlock *pb = ...;
    auto *newInst = new Instruction(..., pb);
    

    これは、特に長い命令ストリームを作成する場合、はるかにクリーンです。

  • IRBuilder のインスタンスを使用した挿入

    上記のメソッドを使用すると、複数の Instruction を挿入するのが非常に面倒になる可能性があります。IRBuilder は、BasicBlock の末尾または特定の Instruction の前に複数の命令を追加するために使用できる便利なクラスです。また、定数畳み込みと名前付きレジスタの名前変更もサポートしています(IRBuilder のテンプレート引数を参照)。

    次の例では、3つの命令が命令 pi の前に挿入される、IRBuilder の非常に簡単な使用法を示します。最初の2つの命令はCall命令で、3番目の命令は2つの呼び出しの戻り値を乗算します。

    Instruction *pi = ...;
    IRBuilder<> Builder(pi);
    CallInst* callOne = Builder.CreateCall(...);
    CallInst* callTwo = Builder.CreateCall(...);
    Value* result = Builder.CreateMul(callOne, callTwo);
    

    次の例は上記の例と似ていますが、作成された IRBuilderBasicBlock pb の末尾に命令を挿入するという点が異なります。

    BasicBlock *pb = ...;
    IRBuilder<> Builder(pb);
    CallInst* callOne = Builder.CreateCall(...);
    CallInst* callTwo = Builder.CreateCall(...);
    Value* result = Builder.CreateMul(callOne, callTwo);
    

    IRBuilder の実用的な使用については、Kaleidoscopeチュートリアルを参照してください。

命令の削除

BasicBlock を形成する既存の命令シーケンスから命令を削除するのは非常に簡単です。命令の eraseFromParent() メソッドを呼び出すだけです。たとえば、

Instruction *I = .. ;
I->eraseFromParent();

これにより、命令がその包含基本ブロックからリンク解除され、削除されます。命令を包含基本ブロックからリンク解除するだけで、削除したくない場合は、removeFromParent() メソッドを使用できます。

命令を別の値に置き換える

個々の命令を置き換える

"llvm/Transforms/Utils/BasicBlockUtils.h" を含めることで、ReplaceInstWithValue および ReplaceInstWithInst の2つの非常に便利な置換関数を使用できます。

命令の削除
  • ReplaceInstWithValue

    この関数は、特定の命令のすべての使用を値で置き換え、元の命令を削除します。次の例は、単一の整数のメモリを割り当てる特定の AllocaInst の結果を、整数へのヌルポインタで置換する方法を示しています。

    AllocaInst* instToReplace = ...;
    BasicBlock::iterator ii(instToReplace);
    
    ReplaceInstWithValue(ii, Constant::getNullValue(PointerType::getUnqual(Type::Int32Ty)));
    
  • ReplaceInstWithInst

    この関数は、特定の命令を別の命令に置き換え、新しい命令を古い命令があった場所の基本ブロックに挿入し、古い命令の使用を新しい命令に置き換えます。次の例は、1つの AllocaInst を別の AllocaInst に置き換える方法を示しています。

    AllocaInst* instToReplace = ...;
    BasicBlock::iterator ii(instToReplace);
    
    ReplaceInstWithInst(instToReplace->getParent(), ii,
                        new AllocaInst(Type::Int32Ty, 0, "ptrToReplacedInt"));
    
UsersとValueの複数の使用を置き換える

Value::replaceAllUsesWith および User::replaceUsesOfWith を使用して、一度に複数の使用を変更できます。詳細については、ValueクラスUserクラスのdoxygenドキュメントを参照してください。

GlobalVariablesの削除

モジュールからグローバル変数を削除するのは、命令を削除するのと同じくらい簡単です。まず、削除するグローバル変数へのポインタが必要です。このポインタを使用して、その親であるモジュールから削除します。例えば

GlobalVariable *GV = .. ;

GV->eraseFromParent();

スレッドとLLVM

このセクションでは、クライアントアプリケーション側と、ホストアプリケーション内のJITの両方で、LLVM APIとマルチスレッドの相互作用について説明します。

LLVMのマルチスレッドのサポートは、まだ比較的新しいことに注意してください。バージョン2.5までは、スレッド化されたホストアプリケーションの実行はサポートされていましたが、APIへのスレッド化されたクライアントアクセスはサポートされていませんでした。このユースケースは現在サポートされていますが、クライアントは、マルチスレッドモードでの適切な動作を保証するために、以下に指定されたガイドラインを遵守する必要があります。

Unixのようなプラットフォームでは、LLVMはスレッド化された動作をサポートするためにGCCのアトミック組み込み関数が存在する必要があります。適切な最新のシステムコンパイラのないプラットフォームでマルチスレッド対応のLLVMが必要な場合は、LLVMとLLVM-GCCをシングルスレッドモードでコンパイルし、結果のコンパイラを使用してマルチスレッドサポート付きのLLVMのコピーをビルドすることを検討してください。

llvm_shutdown() を使用した実行の終了

LLVM APIの使用が完了したら、llvm_shutdown() を呼び出して、内部構造に使用されたメモリを解放する必要があります。

ManagedStatic を使用した遅延初期化

ManagedStatic は、LLVM において、グローバル型テーブルなどの静的リソースの静的初期化を実装するために使用されるユーティリティクラスです。シングルスレッド環境では、単純な遅延初期化スキームを実装します。しかし、LLVM がマルチスレッドのサポート付きでコンパイルされている場合、スレッドセーフな遅延初期化を実装するためにダブルチェックロックを使用します。

LLVMContext による分離の実現

LLVMContext は LLVM API の不透明なクラスで、クライアントは同じアドレス空間内で複数の独立した LLVM インスタンスを同時に操作するために使用できます。たとえば、仮想コンパイルサーバーでは、個々の翻訳単位のコンパイルは概念的に他のすべての翻訳単位から独立しており、独立したサーバー スレッドで受信する翻訳単位を同時にコンパイルできることが望ましいでしょう。幸いなことに、LLVMContext が存在することで、まさにこのようなシナリオが可能になります。

概念的には、LLVMContext は分離を提供します。LLVM のインメモリ IR のすべての LLVM エンティティ(ModuleValueTypeConstant など)は LLVMContext に属します。異なるコンテキストのエンティティは相互にやり取りできません。異なるコンテキストの Module をリンクすることはできず、異なるコンテキストの ModuleFunction を追加することもできません。つまり、同じコンテキスト内のエンティティを操作するスレッドがなければ、複数のスレッドで同時にコンパイルしても安全であるということです。

実際には、Type の作成/ルックアップ API を除き、API のごく一部の場所で LLVMContext の明示的な指定が必要です。すべての Type は所有しているコンテキストへの参照を持っているため、他のほとんどのエンティティは自身の Type を調べることで、どのコンテキストに属しているかを判断できます。LLVM IR に新しいエンティティを追加する場合は、このインターフェース設計を維持するようにしてください。

スレッドと JIT

LLVM の「Eager(即時)」JIT コンパイラは、スレッド化されたプログラムで安全に使用できます。複数のスレッドが ExecutionEngine::getPointerToFunction() または ExecutionEngine::runFunction() を同時に呼び出すことができ、複数のスレッドが JIT によって出力されたコードを同時に実行できます。ユーザーは、別のスレッドが IR を変更している可能性がある間、1 つのスレッドだけが特定の LLVMContext の IR にアクセスするようにする必要があります。その 1 つの方法は、JIT 外で IR にアクセスする際に常に JIT ロックを保持することです(JIT は CallbackVH を追加することで IR を *変更* します)。別の方法は、LLVMContext のスレッドからのみ getPointerToFunction() を呼び出すことです。

JIT が遅延コンパイル(ExecutionEngine::DisableLazyCompilation(false) を使用)するように構成されている場合、関数が遅延 JIT された後に呼び出しサイトを更新する際に、現在競合状態があります。特定な遅延スタブを一度に 1 つのスレッドのみが呼び出すことができ、JIT ロックがすべての IR アクセスを保護することを保証すれば、スレッド化されたプログラムで遅延 JIT を使用することは依然として可能ですが、スレッド化されたプログラムではEager(即時)JITのみを使用することをお勧めします。

高度なトピック

このセクションでは、ほとんどのクライアントが意識する必要のない、高度またはあいまいな API について説明します。これらの API は、LLVM システムの内部動作を管理する傾向があり、通常ではない状況でのみアクセスする必要があります。

ValueSymbolTable クラス

ValueSymbolTable (doxygen) クラスは、Function および Module クラスが値定義に名前を付けるために使用するシンボルテーブルを提供します。シンボルテーブルは、任意の Value に名前を提供できます。

ほとんどのクライアントは SymbolTable クラスに直接アクセスしないでください。シンボルテーブル名自体を反復処理する必要がある場合のみ使用する必要があり、これは非常に特殊な目的です。すべての LLVM Value が名前を持っているわけではなく、名前を持たないもの(つまり、空の名前を持つもの)はシンボルテーブルに存在しないことに注意してください。

シンボルテーブルは、begin/end/iterator を使用したシンボルテーブル内の値の反復をサポートし、特定の名前がシンボルテーブルにあるかどうかを確認するクエリ(lookup を使用)をサポートします。ValueSymbolTable クラスは公開ミューテーターメソッドを公開しません。代わりに、値に対して setName を呼び出すだけで、適切なシンボルテーブルに自動的に挿入されます。

User クラスと所有する Use クラスのメモリレイアウト

User (doxygen) クラスは、他の Value インスタンスに対する User の所有権を表現するための基礎を提供します。Use (doxygen) ヘルパークラスは、簿記を行い、 *O(1)* の追加と削除を容易にするために採用されています。

User オブジェクトと Use オブジェクト間の相互作用と関係

User のサブクラスは、Use オブジェクトを組み込むか、ポインターによってアウトオブラインで参照するかを選択できます。混合バリアント(一部の Use がインラインで、その他がハングオフ)は実用的ではなく、同じ User に属する Use オブジェクトが連続した配列を形成するという不変性を壊します。

User (サブ)クラスには 2 つの異なるレイアウトがあります。

  • レイアウト a)

    Use オブジェクトは、User オブジェクトの内側(または固定オフセット)にあり、固定数が存在します。

  • レイアウト b)

    Use オブジェクトは、User オブジェクトからの配列へのポインタによって参照され、その数は可変です。

v2.4 の時点で、各レイアウトは依然として Use の配列の先頭への直接ポインタを持っています。レイアウト a) には必須ではありませんが、簡潔にするために、この冗長性を維持します。User オブジェクトは、持っている Use オブジェクトの数も格納します。(理論的には、以下のスキームを考慮すると、この情報も計算できます。)

特殊な形式の割り当て演算子(operator new)は、次のメモリレイアウトを強制します。

  • レイアウト a) は、Use[] 配列によって User オブジェクトを先頭に付加することによってモデル化されます。

    ...---.---.---.---.-------...
      | P | P | P | P | User
    '''---'---'---'---'-------'''
    
  • レイアウト b) は、Use[] 配列を指すことでモデル化されます。

    .-------...
    | User
    '-------'''
        |
        v
        .---.---.---.---...
        | P | P | P | P |
        '---'---'---'---'''
    

(上記の図ではPは、 Use のメンバー Use::Prev に格納されている Use** を表します)

型階層とポリモーフィックインターフェースの設計

C++ プログラムの型階層でメソッドに仮想ディスパッチを使用することになる傾向がある 2 つの異なるデザインパターンがあります。1 つ目は、階層内の異なる型が機能とセマンティクスの特定のサブセットをモデル化し、これらの型が互いに厳密にネストされている、正真正銘の型階層です。この良い例は、Value または Type 型階層で見ることができます。

2 つ目は、ポリモーフィックインターフェース実装のコレクション全体で動的にディスパッチしたいという要望です。後者のユースケースは、すべての実装が派生およびオーバーライドする抽象インターフェースベースクラスを定義することにより、仮想ディスパッチと継承でモデル化できます。ただし、この実装戦略では、実際には意味のない 「is-a」 関係の存在が強制されます。コードがやり取りしたり、上下に移動したりする可能性のある有用な一般化のネストされた階層は、しばしば存在しません。代わりに、さまざまな実装にディスパッチされる単一のインターフェースがあります。

2番目のユースケースに最適な実装戦略は、ジェネリックプログラミング(「コンパイル時ダックタイピング」や「静的ポリモーフィズム」とも呼ばれます)です。たとえば、ある型パラメータ T のテンプレートは、インターフェースまたはコンセプトに準拠する特定の実装に対してインスタンス化できます。良い例としては、有向グラフのノードをモデル化する任意の型の高度にジェネリックなプロパティがあります。LLVMは、これらを主にテンプレートとジェネリックプログラミングを通じてモデル化します。このようなテンプレートには、LoopInfoBaseDominatorTreeBase が含まれます。このタイプのポリモーフィズムが本当に動的ディスパッチを必要とする場合は、コンセプトベースのポリモーフィズムと呼ばれる手法を使用して一般化できます。このパターンは、実装内部での型消去のために非常に限定された形式の仮想ディスパッチを使用して、テンプレートのインターフェースと動作をエミュレートします。この手法の例は、PassManager.h システムで見つけることができます。また、Sean Parent氏によるいくつかの講演や論文で、この手法についてより詳しく紹介されています。

  1. 継承は悪の基本クラスである - この手法について説明したGoingNative 2013の講演で、おそらく始めるのに最適な場所です。

  2. 値セマンティクスとコンセプトベースのポリモーフィズム - この手法についてより詳しく説明したC++Now! 2012の講演です。

  3. Sean Parent氏の論文とプレゼンテーション - スライド、ビデオ、場合によってはコードへのリンクが満載のGitHubプロジェクトです。

型階層(タグ付きまたは仮想ディスパッチのいずれかを使用)を作成する場合と、テンプレートまたはコンセプトベースのポリモーフィズムを使用する場合を決定する際には、インターフェース境界上の意味的に意味のある型である抽象基本クラスの何らかの改良があるかどうかを検討してください。ルート抽象インターフェースよりも洗練されたものが、セマンティックモデルの部分的な拡張として語ることが無意味である場合、ユースケースはポリモーフィズムにより適合する可能性が高く、仮想ディスパッチの使用は避ける必要があります。ただし、一方または他方の手法を使用する必要がある緊急の状況がある可能性があります。

型階層を導入する必要がある場合は、C++コードでより一般的なオープン継承モデルと仮想ディスパッチではなく、手動のタグ付きディスパッチやRTTIを使用した明示的にクローズされた型階層を使用することを推奨します。これは、LLVMがライブラリの利用者がコア型を拡張することをほとんど奨励しておらず、階層のクローズドでタグディスパッチされた性質を活用して、大幅に効率的なコードを生成するためです。また、型階層の使用法の多くは、共通インターフェースを介した動的ディスパッチではなく、タグベースのパターンマッチングにより適合していることがわかりました。LLVM内では、この設計を容易にするためのカスタムヘルパーを構築しています。このドキュメントのisaとdyn_castに関するセクションと、このパターンをLLVMヘルパーで使用するために実装する方法について説明している詳細なドキュメントを参照してください。

ABI破壊チェック

LLVM C++ ABIを変更するチェックとアサートは、プリプロセッサシンボルLLVM_ENABLE_ABI_BREAKING_CHECKSに基づいています。つまり、LLVM_ENABLE_ABI_BREAKING_CHECKSでビルドされたLLVMライブラリは、定義なしでビルドされたLLVMライブラリとABI互換性がありません。デフォルトでは、アサーションをオンにするとLLVM_ENABLE_ABI_BREAKING_CHECKSもオンになるため、デフォルトの+Assertsビルドは、デフォルトの-AssertsビルドとABI互換性がありません。+Assertsビルドと-Assertsビルド間でABI互換性を必要とするクライアントは、CMakeビルドシステムを使用して、LLVM_ENABLE_ASSERTIONSとは独立してLLVM_ENABLE_ABI_BREAKING_CHECKSを設定する必要があります。

コアLLVMクラス階層リファレンス

#include "llvm/IR/Type.h"

ヘッダーソース: Type.h

Doxygen情報: Typeクラス

コアLLVMクラスは、検査または変換されるプログラムを表すための主要な手段です。コアLLVMクラスは、include/llvm/IRディレクトリのヘッダーファイルで定義され、lib/IRディレクトリで実装されています。歴史的な理由から、このライブラリは予想されるlibLLVMIR.soではなく、libLLVMCore.soと呼ばれていることは注目に値します。

Typeクラスと派生型

Typeは、すべての型クラスのスーパークラスです。すべてのValueにはTypeがあります。Typeは直接インスタンス化できず、そのサブクラスを通じてのみインスタンス化できます。特定のプリミティブ型(VoidTypeLabelTypeFloatType、およびDoubleType)には、隠されたサブクラスがあります。これらのサブクラスは、Typeクラスが提供するもの以外に、Typeの他のサブクラスと区別する以外に役立つ機能を提供しないため、非表示になっています。

その他すべての型は、DerivedTypeのサブクラスです。型には名前を付けることができますが、これは必須ではありません。任意の時点で、特定の形状のインスタンスが1つだけ存在します。これにより、Typeインスタンスのアドレス等価性を使用して型の等価性を実行できます。つまり、2つのType*値が与えられた場合、ポインタが同じであれば、型は同じです。

重要なパブリックメソッド

  • bool isIntegerTy() const: 任意の整数型の場合はtrueを返します。

  • bool isFloatingPointTy(): これが5つの浮動小数点型のいずれかである場合はtrueを返します。

  • bool isSized(): 型に既知のサイズがある場合はtrueを返します。サイズがないものは、抽象型、ラベル、およびvoidです。

重要な派生型

IntegerType

任意のビット幅の整数型を表すDerivedTypeのサブクラス。IntegerType::MIN_INT_BITS (1) と IntegerType::MAX_INT_BITS (~800万) の間の任意のビット幅を表すことができます。

  • static const IntegerType* get(unsigned NumBits): 特定のビット幅の整数型を取得します。

  • unsigned getBitWidth() const: 整数型のビット幅を取得します。

SequentialType

これはArrayTypeとVectorTypeによってサブクラス化されます。

  • const Type * getElementType() const: 順次型の各要素の型を返します。

  • uint64_t getNumElements() const: 順次型の要素数を返します。

ArrayType

これはSequentialTypeのサブクラスであり、配列型のインターフェースを定義します。

PointerType

ポインタ型のTypeのサブクラス。

VectorType

ベクトル型のSequentialTypeのサブクラス。ベクトル型はArrayTypeに似ていますが、ArrayTypeはファーストクラス型ではないのに対し、ベクトル型はファーストクラス型であるため区別されます。ベクトル型はベクトル演算に使用され、通常は整数または浮動小数点型の小さなベクトルです。

StructType

構造体型のDerivedTypesのサブクラス。

FunctionType

関数型のDerivedTypesのサブクラス。

  • bool isVarArg() const: 可変長引数関数である場合はtrueを返します。

  • const Type * getReturnType() const: 関数の戻り型を返します。

  • const Type * getParamType (unsigned i): i番目のパラメータの型を返します。

  • const unsigned getNumParams() const: 仮パラメータの数を返します。

Moduleクラス

#include "llvm/IR/Module.h"

ヘッダーソース: Module.h

Doxygen情報: Moduleクラス

Module クラスは、LLVMプログラムに存在する最上位の構造を表します。LLVMモジュールは、実質的に元のプログラムの翻訳単位であるか、リンカによってマージされた複数の翻訳単位の組み合わせです。Module クラスは、Function のリスト、GlobalVariable のリスト、および SymbolTable を追跡します。さらに、一般的な操作を簡単にするためのいくつかの便利なメンバー関数が含まれています。

Module クラスの重要なパブリックメンバー

  • Module::Module(std::string name = "")

    Module の構築は簡単です。オプションで名前(おそらく翻訳単位の名前に基づく)を指定できます。

  • Module::iterator - 関数リストイテレータの typedef
    Module::const_iterator - const_iterator の typedef。
    begin(), end(), size(), empty()

    これらは、Module オブジェクトの Function リストの内容に簡単にアクセスできるようにする転送メソッドです。

  • Module::FunctionListType &getFunctionList()

    Function のリストを返します。リストを更新したり、転送メソッドがない複雑なアクションを実行したりする必要がある場合に、これを使用する必要があります。


  • Module::global_iterator - グローバル変数リストイテレータの typedef
    Module::const_global_iterator - const_iterator の typedef。
    Module::insertGlobalVariable() - グローバル変数をリストに挿入します。
    Module::removeGlobalVariable() - グローバル変数をリストから削除します。
    Module::eraseGlobalVariable() - グローバル変数をリストから削除して、削除します。
    global_begin(), global_end(), global_size(), global_empty()

    これらは、Module オブジェクトの GlobalVariable リストの内容に簡単にアクセスできるようにする転送メソッドです。


  • SymbolTable *getSymbolTable()

    この ModuleSymbolTable への参照を返します。


  • Function *getFunction(StringRef Name) const

    ModuleSymbolTable で指定された関数を検索します。存在しない場合は、null を返します。

  • FunctionCallee getOrInsertFunction(const std::string &Name, const FunctionType *T)

    ModuleSymbolTable で指定された関数を検索します。存在しない場合は、関数の外部宣言を追加して返します。既に存在する関数のシグネチャが要求されたシグネチャと一致しない場合があることに注意してください。したがって、結果を直接 EmitCall に渡すという一般的な使用を可能にするために、戻り値の型は、単に予期しないシグネチャを持つ可能性のある Function* ではなく、{FunctionType *T, Constant *FunctionPtr} の構造体です。

  • std::string getTypeName(const Type *Ty)

    指定された TypeSymbolTable に少なくとも1つのエントリがある場合は、それを返します。それ以外の場合は、空の文字列を返します。

  • bool addTypeName(const std::string &Name, const Type *Ty)

    Name から Ty へのマッピングを SymbolTable に挿入します。この名前のエントリが既に存在する場合は、true が返され、SymbolTable は変更されません。

Value クラス

#include "llvm/IR/Value.h"

ヘッダーソース: Value.h

doxygen 情報: Value クラス

Value クラスは、LLVMソースベースで最も重要なクラスです。これは、命令のオペランドとして(特に)使用できる型付きの値を表します。Value には、ConstantArgument など、多くの異なる型があります。InstructionFunction さえも Value です。

特定の Value は、プログラムのLLVM表現で何度も使用される場合があります。たとえば、関数への入力引数(Argument クラスのインスタンスで表されます)は、引数を参照する関数内のすべての命令によって「使用」されます。この関係を追跡するために、Value クラスは、それを使用しているすべての User のリストを保持します(User クラスは、Value を参照できるLLVMグラフ内のすべてのノードの基本クラスです)。この使用リストは、LLVMがプログラム内のdef-use情報を表現する方法であり、以下に示す use_* メソッドを通じてアクセスできます。

LLVMは型付きの表現であるため、すべてのLLVM Value は型付きであり、この TypegetType() メソッドを通じて利用できます。さらに、すべてのLLVM値には名前を付けることができます。Value の「名前」は、LLVMコードで出力されるシンボリック文字列です。

%foo = add i32 1, 2

この命令の名前は「foo」です。注意 値の名前は欠落している場合がある(空の文字列)ため、名前はデバッグ(ソースコードを読みやすくしたり、デバッグ出力をしたりするなど)にのみ使用する必要があり、値を追跡したり、それらの間でマッピングしたりするために使用しないでください。この目的には、Value 自体へのポインタの std::map を代わりに使用してください。

LLVMの重要な側面の1つは、SSA変数とそれを生成する操作の間に区別がないことです。このため、命令によって生成された値(または、たとえば入力引数として利用できる値)への参照は、この値を表すクラスのインスタンスへの直接ポインタとして表されます。これに慣れるまで時間がかかる場合がありますが、表現が簡略化され、操作が容易になります。

Value クラスの重要なパブリックメンバー

  • Value::use_iterator - use-list を反復処理するためのイテレータの typedef
    Value::const_use_iterator - use-list を反復処理するための const_iterator の typedef
    unsigned use_size() - 値のユーザーの数を返します。
    bool use_empty() - ユーザーがいない場合は true を返します。
    use_iterator use_begin() - use-list の先頭へのイテレータを取得します。
    use_iterator use_end() - use-list の末尾へのイテレータを取得します。
    User *use_back() - リストの最後の要素を返します。

    これらのメソッドは、LLVMのdef-use情報にアクセスするためのインターフェースです。LLVMの他のすべてのイテレータと同様に、命名規則はSTLで定義された規則に従います。

  • Type *getType() const このメソッドは、Value の Type を返します。

  • bool hasName() const
    std::string getName() const
    void setName(const std::string &Name)

    この一連のメソッドは、Value に名前をアクセスして割り当てるために使用されます。上記の注意点に注意してください。

  • void replaceAllUsesWith(Value *V)

    このメソッドは、Value の使用リストをトラバースし、現在の値のすべての User を、代わりに「V」を参照するように変更します。たとえば、命令が常に定数を生成することを検出した場合(たとえば、定数畳み込みによって)、次のように命令のすべての使用を定数に置き換えることができます。

    Inst->replaceAllUsesWith(ConstVal);
    

User クラス

#include "llvm/IR/User.h"

ヘッダーソース: User.h

doxygen 情報: User クラス

スーパークラス: Value

User クラスは、Value を参照する可能性のあるすべてのLLVMノードの共通の基本クラスです。これは、User が参照しているすべての Value である「オペランド」のリストを公開します。User クラス自体は Value のサブクラスです。

User のオペランドは、参照先の LLVM の Value を直接指します。LLVM は静的単一代入 (SSA) 形式を使用しているため、参照される定義は 1 つしか存在できず、この直接的な接続が可能になります。この接続は、LLVM での use-def 情報を実現します。

User クラスの重要なパブリックメンバ

User クラスは、インデックスアクセスインターフェースとイテレータベースのインターフェースという 2 つの方法でオペランドリストを公開します。

  • Value *getOperand(unsigned i)
    unsigned getNumOperands()

    これらの 2 つのメソッドは、User のオペランドを直接アクセスできる便利な形式で公開します。

  • User::op_iterator - オペランドリストに対するイテレータの typedef
    op_iterator op_begin() - オペランドリストの先頭へのイテレータを取得します。
    op_iterator op_end() - オペランドリストの末尾へのイテレータを取得します。

    これらのメソッドを組み合わせることで、User のオペランドへのイテレータベースのインターフェースを構成します。

Instruction クラス

#include "llvm/IR/Instruction.h"

ヘッダーソース: Instruction.h

Doxygen 情報: Instruction クラス

スーパークラス: User, Value

Instruction クラスは、すべての LLVM 命令の共通の基本クラスです。いくつかのメソッドしか提供しませんが、非常に一般的に使用されるクラスです。Instruction クラス自体で追跡される主なデータは、オペコード (命令の種類) と、Instruction が埋め込まれている親の BasicBlock です。特定の種類の命令を表すには、Instruction の多数のサブクラスのいずれかが使用されます。

Instruction クラスは User クラスを継承しているため、オペランドは他の User の場合と同じ方法 (getOperand()/getNumOperands() メソッドと op_begin()/op_end() メソッドを使用) でアクセスできます。Instruction クラスにとって重要なファイルは、llvm/Instruction.def ファイルです。このファイルには、LLVM のさまざまな種類の命令に関するメタデータが含まれています。オペコードとして使用される enum 値 (たとえば、Instruction::AddInstruction::ICmp など) や、命令を実装する Instruction の具体的なサブクラス (たとえば、BinaryOperatorCmpInst など) が記述されています。残念ながら、このファイルでのマクロの使用により doxygen が混乱するため、これらの enum 値は doxygen 出力に正しく表示されません。

Instruction クラスの重要なサブクラス

  • BinaryOperator

    このサブクラスは、比較命令を除く、オペランドが同じ型でなければならないすべての 2 つのオペランド命令を表します。

  • CastInst このサブクラスは、12 個のキャスト命令の親です。キャスト命令に対する共通の操作を提供します。

  • CmpInst

    このサブクラスは、2 つの比較命令である ICmpInst (整数オペランド) と FCmpInst (浮動小数点オペランド) を表します。

Instruction クラスの重要なパブリックメンバ

  • BasicBlock *getParent()

    この Instruction が埋め込まれている BasicBlock を返します。

  • bool mayWriteToMemory()

    命令がメモリに書き込む場合 (つまり、callfreeinvoke、または store の場合) は true を返します。

  • unsigned getOpcode()

    Instruction のオペコードを返します。

  • Instruction *clone() const

    指定された命令の別のインスタンスを返します。これは、命令に親がない (つまり、BasicBlock に埋め込まれていない) ことと、名前がないことを除き、元の命令とまったく同じです。

Constant クラスとそのサブクラス

Constant は、さまざまなタイプの定数の基本クラスを表します。ConstantInt、ConstantArray などが、さまざまなタイプの定数を表すためにサブクラス化されています。GlobalValue もサブクラスであり、グローバル変数または関数のアドレスを表します。

Constant の重要なサブクラス

  • ConstantInt: この Constant のサブクラスは、任意の幅の整数定数を表します。

    • const APInt& getValue() const: この定数の基礎となる値である APInt 値を返します。

    • int64_t getSExtValue() const: 基礎となる APInt 値を符号拡張によって int64_t に変換します。APInt の値 (ビット幅ではなく) が大きすぎて int64_t に収まらない場合は、アサーションが発生します。このため、このメソッドの使用は推奨されません。

    • uint64_t getZExtValue() const: 基礎となる APInt 値をゼロ拡張によって uint64_t に変換します。APInt の値 (ビット幅ではなく) が大きすぎて uint64_t に収まらない場合は、アサーションが発生します。このため、このメソッドの使用は推奨されません。

    • static ConstantInt* get(const APInt& Val): Val によって提供される値を表す ConstantInt オブジェクトを返します。型は、Val のビット幅に対応する IntegerType として暗黙的に指定されます。

    • static ConstantInt* get(const Type *Ty, uint64_t Val): 整数型 TyVal によって提供される値を表す ConstantInt オブジェクトを返します。

  • ConstantFP: このクラスは浮動小数点定数を表します。

    • double getValue() const: この定数の基礎となる値を返します。

  • ConstantArray: これは定数配列を表します。

    • const std::vector<Use> &getValues() const: この配列を構成するコンポーネント定数のベクターを返します。

  • ConstantStruct: これは定数構造体をを表します。

    • const std::vector<Use> &getValues() const: この配列を構成するコンポーネント定数のベクターを返します。

  • GlobalValue: これは、グローバル変数または関数のいずれかを表します。どちらの場合も、値は (リンク後の) 定数の固定アドレスです。

GlobalValue クラス

#include "llvm/IR/GlobalValue.h"

ヘッダーソース: GlobalValue.h

Doxygen 情報: GlobalValue クラス

スーパークラス: Constant, User, Value

グローバル値 (GlobalVariable または Function) は、すべての Function の本体で可視である唯一の LLVM 値です。これらはグローバルスコープで可視であるため、異なる翻訳単位で定義された他のグローバルとのリンクにも対応します。リンクプロセスを制御するために、GlobalValue はそのリンケージルールを認識します。具体的には、GlobalValue は、LinkageTypes 列挙型で定義されているように、内部リンケージまたは外部リンケージのどちらを持っているかを認識します。

GlobalValue が内部リンケージ (C で static であることと同等) を持つ場合、現在の翻訳単位外のコードからは可視にならず、リンクには参加しません。外部リンケージを持つ場合は、外部コードに対して可視であり、リンクに参加します。リンケージ情報に加えて、GlobalValue は、現在どの Module の一部であるかを追跡します。

GlobalValueはメモリオブジェクトであるため、常にそのアドレスによって参照されます。そのため、グローバルのは常にその内容へのポインタになります。この点は、GetElementPtrInst命令を使用する際に重要です。このポインタは最初に間接参照する必要があるからです。例えば、24個のintの配列である[24 x i32]型のGlobalVariableGlobalValueのサブクラス)がある場合、そのGlobalVariableはその配列へのポインタになります。この配列の最初の要素のアドレスとGlobalVariableの値は同じですが、型は異なります。GlobalVariableの型は[24 x i32]です。最初の要素の型はi32です。このため、グローバル値にアクセスするには、まずGetElementPtrInstでポインタを間接参照し、それからその要素にアクセスする必要があります。この点については、LLVM言語リファレンスマニュアルで説明されています。

GlobalValueクラスの重要な公開メンバ

  • bool hasInternalLinkage() const
    bool hasExternalLinkage() const
    void setInternalLinkage(bool HasInternalLinkage)

    これらのメソッドは、GlobalValueのリンケージ特性を操作します。

  • Module *getParent()

    これは、GlobalValueが現在埋め込まれているModuleを返します。

Functionクラス

#include "llvm/IR/Function.h"

ヘッダーソース: Function.h

Doxygen情報: Functionクラス

スーパークラス: GlobalValue, Constant, User, Value

Functionクラスは、LLVMにおける単一の手続きを表します。実際には、大量のデータを追跡する必要があるため、LLVMの階層構造の中でより複雑なクラスの一つです。Functionクラスは、BasicBlockのリスト、形式的なArgumentのリスト、およびSymbolTableを追跡します。

BasicBlockのリストは、Functionオブジェクトの最も一般的に使用される部分です。このリストは、関数内のブロックの暗黙的な順序を課し、バックエンドがどのようにコードを配置するかを示します。さらに、最初のBasicBlockは、Functionの暗黙的なエントリノードです。LLVMでは、この初期ブロックに明示的に分岐することは許可されていません。暗黙的な出口ノードはなく、実際には単一のFunctionから複数の出口ノードが存在する可能性があります。BasicBlockリストが空の場合、これはFunctionが実際には関数宣言であることを示します。関数の実際の本体はまだリンクされていません。

BasicBlockのリストに加えて、Functionクラスは、関数が受け取る形式的なArgumentのリストも追跡します。このコンテナは、BasicBlockリストがBasicBlockに対して行うのと同じように、Argumentノードの寿命を管理します。

SymbolTableは、名前で値を検索する必要がある場合にのみ使用される、非常にまれなLLVM機能です。それ以外にも、SymbolTableは、関数本体内のInstructionBasicBlock、またはArgumentの名前の間で競合がないことを確認するために内部で使用されます。

FunctionGlobalValueであり、したがってConstantでもあることに注意してください。関数の値は(リンク後の)アドレスであり、定数であることが保証されています。

Functionの重要な公開メンバ

  • Function(const FunctionType *Ty, LinkageTypes Linkage, const std::string &N = "", Module* Parent = 0)

    プログラムに追加する新しいFunctionを作成する必要がある場合に使用されるコンストラクタ。コンストラクタは、作成する関数の型と、関数が持つ必要のあるリンケージの種類を指定する必要があります。FunctionType引数は、関数の形式的な引数と戻り値を指定します。同じFunctionTypeの値を使用して、複数の関数を作成できます。Parent引数は、関数が定義されているModuleを指定します。この引数が指定されている場合、関数は自動的にそのモジュールの関数のリストに挿入されます。

  • bool isDeclaration()

    Functionが本体を定義しているかどうかを返します。関数が「外部」の場合、本体を持たないため、異なる翻訳単位で定義された関数とのリンクによって解決する必要があります。

  • Function::iterator - 基本ブロックリストイテレータのTypedef
    Function::const_iterator - const_iteratorのTypedef。
    begin(), end(), size(), empty(), insert(), splice(), erase()

    これらは、FunctionオブジェクトのBasicBlockリストの内容に簡単にアクセスできるようにする転送メソッドです。

  • Function::arg_iterator - 引数リストイテレータのTypedef
    Function::const_arg_iterator - const_iteratorのTypedef。
    arg_begin(), arg_end(), arg_size(), arg_empty()

    これらは、FunctionオブジェクトのArgumentリストの内容に簡単にアクセスできるようにする転送メソッドです。

  • Function::ArgumentListType &getArgumentList()

    Argumentのリストを返します。これは、リストを更新する必要がある場合や、転送メソッドがない複雑なアクションを実行する必要がある場合に使用する必要があります。

  • BasicBlock &getEntryBlock()

    関数のエントリBasicBlockを返します。関数のエントリブロックは常に最初のブロックであるため、これはFunctionの最初のブロックを返します。

  • Type *getReturnType()
    FunctionType *getFunctionType()

    これは、FunctionTypeを走査し、関数の戻り型、または実際の関数のFunctionTypeを返します。

  • SymbolTable *getSymbolTable()

    このFunctionSymbolTableへのポインタを返します。

GlobalVariableクラス

#include "llvm/IR/GlobalVariable.h"

ヘッダーソース: GlobalVariable.h

Doxygen情報: GlobalVariableクラス

スーパークラス: GlobalValue, Constant, User, Value

グローバル変数は、(驚くべきことに)GlobalVariableクラスで表されます。関数と同様に、GlobalVariableGlobalValueのサブクラスであり、そのアドレスによって常に参照されます(グローバル値はメモリに存在する必要があるため、その「名前」は定数アドレスを指します)。詳細については、GlobalValueを参照してください。グローバル変数は初期値(Constantである必要があります)を持つことができ、初期化子がある場合、「定数」としてマークされることもあります(その内容が実行時に決して変更されないことを示します)。

GlobalVariableクラスの重要な公開メンバ

  • GlobalVariable(const Type *Ty, bool isConstant, LinkageTypes &Linkage, Constant *Initializer = 0, const std::string &Name = "", Module* Parent = 0)

    指定された型の新しいグローバル変数を生成します。 isConstant が true の場合、グローバル変数はプログラム内で変更されないものとしてマークされます。Linkage パラメータは、変数のリンケージの種類(internal、external、weak、linkonce、appending)を指定します。リンケージが InternalLinkage、WeakAnyLinkage、WeakODRLinkage、LinkOnceAnyLinkage、または LinkOnceODRLinkage の場合、結果のグローバル変数は内部リンケージを持ちます。AppendingLinkage は、変数のすべてのインスタンス(異なる翻訳単位内)を連結して単一の変数にします。ただし、これは配列にのみ適用されます。リンケージの種類の詳細については、LLVM 言語リファレンスを参照してください。オプションで、グローバル変数の初期化子、名前、および変数を配置するモジュールも指定できます。

  • bool isConstant() const

    これが実行時に変更されないことがわかっているグローバル変数の場合に true を返します。

  • bool hasInitializer()

    この GlobalVariable が初期化子を持つ場合に true を返します。

  • Constant *getInitializer()

    GlobalVariable の初期値を返します。初期化子がない場合は、このメソッドを呼び出すことは許可されていません。

BasicBlock クラス

#include "llvm/IR/BasicBlock.h"

ヘッダーソース: BasicBlock.h

doxygen 情報: BasicBlock クラス

スーパークラス: Value

このクラスは、コンパイラコミュニティで一般的に基本ブロックとして知られる、コードの単一エントリ単一出口のセクションを表します。BasicBlock クラスは、ブロックの本体を形成する 命令 のリストを保持します。言語の定義に合わせて、この命令リストの最後の要素は常にターミネータ命令です。

ブロックを構成する命令のリストの追跡に加えて、BasicBlock クラスは、それが埋め込まれている Function も追跡します。

BasicBlock 自体は、分岐などの命令で参照され、スイッチテーブルに入る可能性があるため、Value であることに注意してください。BasicBlock の型は label です。

BasicBlock クラスの重要なパブリックメンバー

  • BasicBlock(const std::string &Name = "", Function *Parent = 0)

    BasicBlock コンストラクタは、関数に挿入するための新しい基本ブロックを作成するために使用されます。コンストラクタは、オプションで新しいブロックの名前と、挿入先の Function を受け取ります。Parent パラメータが指定されている場合、新しい BasicBlock は指定された Function の末尾に自動的に挿入されます。指定されていない場合、BasicBlock は手動で Function に挿入する必要があります。

  • BasicBlock::iterator - 命令リストイテレータの Typedef
    BasicBlock::const_iterator - const_iterator の Typedef。
    begin()end()front()back()size()empty()splice() 命令リストにアクセスするための STL スタイルの関数。

    これらのメソッドと typedef は、同じ名前の標準ライブラリメソッドと同じセマンティクスを持つ転送関数です。これらのメソッドは、操作が簡単な方法で基本ブロックの基になる命令リストを公開します。

  • Function *getParent()

    ブロックが埋め込まれている Function へのポインタを返します。ホームレスの場合は null ポインタを返します。

  • Instruction *getTerminator()

    BasicBlock の末尾にあるターミネータ命令へのポインタを返します。ターミネータ命令がない場合、またはブロックの最後の命令がターミネータでない場合は、null ポインタが返されます。

Argument クラス

この Value のサブクラスは、関数への入力仮引数のインターフェイスを定義します。関数は、その仮引数のリストを保持します。引数は、親関数へのポインタを持ちます。