よくある質問 (FAQ)

ライセンス

LLVMのソースコードを修正し、修正されたソースを再配布できますか?

はい。修正されたソース配布では、著作権表示を保持し、LLVM例外付きApacheライセンスv2.0に記載されている条件に従う必要があります。

LLVMのソースコードを修正し、ソースを再配布せずに、それに基づいてバイナリやその他のツールを再配布できますか?

はい。これが、上記の最初の質問で説明したように、GPLよりも制限の少ないライセンスでLLVMを配布している理由です。

ソースコード

LLVMはどの言語で書かれていますか?

LLVMのツールとライブラリはすべて、STLを多用してC++で記述されています。

LLVMのソースコードはどの程度移植性がありますか?

LLVMのソースコードは、ほとんどの最新のUnix系オペレーティングシステムに移植可能である必要があります。LLVMは、Windowsシステムでも優れたサポートを備えています。コードのほとんどは標準C++で記述されており、オペレーティングシステムサービスはサポートライブラリに抽象化されています。LLVMをビルドおよびテストするために必要なツールは、多数のプラットフォームに移植されています。

LLVM IRのSSA表現の仮想レジスタの1つに値を格納するには、どのAPIを使用しますか?

簡単に言うと、できません。何が起こっているかを理解すると、実際には少し間抜けな質問です。基本的に、次のようなコードでは

%result = add i32 %foo, %bar

%resultは、add命令のValueに付けられた名前です。つまり、%resultadd命令です。「代入」は、特定の「仮想レジスタ」に何も明示的に「格納」しません。 "="は、数学的な意味での等号に似ています。

より詳しい説明: IRのテキスト表現を生成するには、他の命令がテキストでそれを参照できるように、各命令に何らかの名前を付ける必要があります。ただし、C++から操作する同型インメモリ表現には、命令が参照する他のValueへのポインターを保持できるため、そのような制限はありません。実際、%1のようなダミーの番号付き一時変数の名前は、インメモリ表現では明示的に表現されていません(Value::getName()を参照)。

ソース言語

どのソース言語がサポートされていますか?

LLVMは現在、Clangを通じてCおよびC++ソース言語を完全にサポートしています。LLVMを使用して他の多くの言語フロントエンドが作成されており、不完全なリストはLLVMを使用するプロジェクトで入手できます。

セルフホスティングのLLVMコンパイラーを作成したいのですが。 LLVMのミドルエンドオプティマイザとバックエンドコードジェネレーターとどのようにインターフェースを取ればよいですか?

コンパイラのフロントエンドは、LLVM中間表現(IR)形式でモジュールを作成することにより、LLVMと通信します。言語自体(C++ではなく)で言語のコンパイラを作成する場合、フロントエンドからLLVM IRを生成するには、主に3つの方法があります。

  1. 言語のFFI(外部関数インターフェース)を使用して、LLVMライブラリコードを呼び出します。

  • 利点: LLVM IR、.ll構文、および.bc形式への変更を最適に追跡

  • 利点: emit/parseオーバーヘッドなしでLLVM最適化パスを実行できる

  • 利点: JITコンテキストによく適応

  • 欠点: 大量の醜いグルーコードを作成する必要がある

  1. コンパイラのネイティブ言語からLLVMアセンブリを出力します。

  • 利点: 開始するのが非常に簡単

  • 欠点: ミドルエンドとのインターフェースを取る際、.llパーサーはビットコードリーダーよりも遅い

  • 欠点: IRへの変更を追跡するのが難しい場合がある

  1. コンパイラのネイティブ言語からLLVMビットコードを出力します。

  • 利点: ミドルエンドとのインターフェースを取る際、より効率的なビットコードリーダーを使用できる

  • 欠点: 言語でLLVM IRオブジェクトモデルとビットコードライターを再設計する必要がある

  • 欠点: IRへの変更を追跡するのが難しい場合がある

最初のオプションを使用する場合、include/llvm-cのCバインディングは、ほとんどの言語がCとのインターフェースを強くサポートしているため、非常に役立ちます。マネージコードからCを呼び出す際の最も一般的なハードルは、ガベージコレクターとのインターフェースです。Cインターフェースは、メモリ管理をほとんど必要としないように設計されているため、この点では簡単です。

コンパイラーを構築するためのより高レベルのソース言語構造にはどのようなサポートがありますか?

現在、あまりありません。LLVMは、コード表現に役立つ中間表現をサポートしていますが、ほとんどのコンパイラーに必要な高レベル(抽象構文木)表現はサポートしていません。字句分析や意味解析のための機能はありません。

GetElementPtr命令がわかりません。教えてください!

誤解されがちな GEP 命令をご覧ください。

CおよびC++フロントエンドの使用

CまたはC++コードをプラットフォームに依存しないLLVMビットコードにコンパイルできますか?

いいえ。CとC++は本質的にプラットフォーム依存の言語です。この最も明白な例は、プリプロセッサです。Cコードが移植可能になる一般的な方法は、プリプロセッサを使用してプラットフォーム固有のコードを含めることです。実際には、他のプラットフォームに関する情報はプリプロセス後に失われるため、結果はプリプロセスがターゲットにしていたプラットフォームに本質的に依存します。

別の例はsizeofです。sizeof(long)がプラットフォームによって異なるのは一般的です。ほとんどのCフロントエンドでは、sizeofはすぐに定数に展開されるため、プラットフォーム固有の詳細がハードワイヤリングされます。

また、多くのプラットフォームではABIをCの観点から定義しており、LLVMはCよりも低レベルであるため、フロントエンドは現在、プラットフォームのABIに準拠させるためにプラットフォーム固有のIRを生成する必要があります。

デモページで生成されたコードに関する質問

#include <iostream> を行うと発生する llvm.global_ctors_GLOBAL__I_a... のようなものは何ですか?

C++の翻訳単位に <iostream> ヘッダーを #include すると、ファイルはほぼ間違いなく std::cin / std::cout / ... のグローバルオブジェクトを使用します。しかし、C++は異なる翻訳単位における静的オブジェクト間の初期化順序を保証していません。そのため、例えば、.cppファイル内の静的コンストラクタ/デストラクタが std::cout を使用した場合、オブジェクトは必ずしも使用前に自動的に初期化されるとは限りません。

これらのシナリオで std::cout などが正しく機能するように、私たちが使用するSTLは、 <iostream> をインクルードするすべての翻訳単位で作成される静的オブジェクトを宣言します。このオブジェクトは、ファイル内でグローバルiostreamオブジェクトが使用される可能性のある前に、それらのオブジェクトを初期化および破棄する静的コンストラクタとデストラクタを持っています。 .ll ファイルに表示されるコードは、コンストラクタとデストラクタの登録コードに対応します。

デモページでコンパイラによって生成されたLLVMコードを理解しやすくしたい場合は、値の出力に iostream の代わりに printf() を使用することを検討してください。

自分のコードはどこに行ったのですか?

LLVMデモページを使用している場合、入力したすべてのコードがどうなったのか疑問に思うことがよくあるかもしれません。デモスクリプトはLLVMオプティマイザーを通してコードを実行していることを忘れないでください。そのため、コードが実際に役に立つことを何もしない場合、すべて削除される可能性があります。

これを防ぐには、コードが実際に必要であることを確認してください。たとえば、何らかの式を計算する場合は、ローカル変数に残すのではなく、関数の値を返します。オプティマイザーを本当に制約したい場合は、volatile グローバル変数から読み書きできます。

コードに表示されるこの「undef」とは何ですか?

undef は、定義されていない値を表すLLVMの方法です。使用する前に変数を初期化しないと、これらが現れることがあります。例えば、C言語の関数

int X() { int i; return i; }

は、「ret i32 undef」とコンパイルされます。「i」に値が指定されていないためです。

instcombine + simplifycfg が、呼び出し規約が一致しない関数への呼び出しを「到達不能」に変えるのはなぜですか?なぜ検証ツールで拒否しないのですか?

これは、カスタムの呼び出し規約を使用しているフロントエンドの作成者がよく遭遇する問題です。関数と関数への各呼び出しの両方で正しい呼び出し規約を設定する必要があります。例えば、このコード

define fastcc void @foo() {
    ret void
}
define void @bar() {
    call void @foo()
    ret void
}

は、次のように最適化されます。

define fastcc void @foo() {
    ret void
}
define void @bar() {
    unreachable
}

… 「opt -instcombine -simplifycfg」を使用すると、このようになります。これは、「すべてのコードが消えてしまう」ため、しばしば人々の頭を悩ませます。間接呼び出しが機能するためには、呼び出し元と呼び出し先で呼び出し規約を設定する必要があります。そのため、人々はこの種のことを検証ツールで拒否しないのはなぜかとよく尋ねます。

答えは、このコードは未定義の動作を持つが、違法ではないということです。もしこれを違法にした場合、これを生成する可能性のあるすべての変換が、そうしないことを保証する必要があり、この種の構造を作成できる有効なコード(デッドコード内)が存在します。これが発生する可能性のある状況は非常に不自然ですが、それでも受け入れる必要があります。以下に例を示します。

define fastcc void @foo() {
    ret void
}
define internal void @bar(void()* %FP, i1 %cond) {
    br i1 %cond, label %T, label %F
T:
    call void %FP()
    ret void
F:
    call fastcc void %FP()
    ret void
}
define void @test() {
    %X = or i1 false, false
    call void @bar(void()* @foo, i1 %X)
    ret void
}

この例では、「test」は常に @foo / falsebar に渡します。これにより、正しい呼び出し規約で動的に呼び出されることが保証されます(したがって、コードは完全に定義されています)。これをインライナーに通すと、次のようになります(明示的な「or」があるのは、インライナーが多くのものをデッドコード除去しないようにするためです)。

define fastcc void @foo() {
    ret void
}
define void @test() {
    %X = or i1 false, false
    br i1 %X, label %T.i, label %F.i
T.i:
    call void @foo()
    br label %bar.exit
F.i:
    call fastcc void @foo()
    br label %bar.exit
bar.exit:
    ret void
}

ここで、インライン化パスが、間違った呼び出し規約で @foo への未定義の呼び出しを行ったことがわかります。インライナーがこの種のことを知っている必要はまったくありません。したがって、これは有効なコードである必要があります。この場合、デッドコード除去は未定義のコードを簡単に削除できます。ただし、 %X@test の入力引数だった場合、インライナーはこれを生成します。

define fastcc void @foo() {
    ret void
}

define void @test(i1 %X) {
    br i1 %X, label %T.i, label %F.i
T.i:
    call void @foo()
    br label %bar.exit
F.i:
    call fastcc void @foo()
    br label %bar.exit
bar.exit:
    ret void
}

これの興味深い点は、コードが適切に定義されているためには、 %Xfalseでなければならないということですが、デッドコード除去を行っても、壊れた呼び出しを到達不能として削除することはできません。しかし、 instcombine / simplifycfg が未定義の呼び出しを到達不能に変換するため、到達不能になる条件分岐が発生します。到達不能への分岐は決して発生しないため、「-inline -instcombine -simplifycfg」は次を生成できます。

define fastcc void @foo() {
   ret void
}
define void @test(i1 %X) {
F.i:
   call fastcc void @foo()
   ret void
}