LLVMの拡張:命令、組込み関数、型の追加など

はじめにおよび警告

LLVMを使用する際に、研究プロジェクトや実験のためにLLVMをカスタマイズしたいと思うかもしれません。その時点で、新しい基本型、新しい組込み関数、あるいは全く新しい命令などをLLVMに追加する必要があることに気づくかもしれません。

このことに気づいたら、一度立ち止まって考えてください。本当にLLVMを拡張する必要がありますか?それは、現在のLLVMではサポートされていない新しい基本的な機能ですか、それとも既に存在するLLVM要素から合成できますか?確信が持てない場合は、LLVMフォーラムで質問してください。理由は、LLVMを拡張するには、拡張機能で使用したいさまざまなパスすべてを更新する必要があり、多数のLLVM分析と変換があるため、かなりの作業になる可能性があるからです。

命令を追加するよりも、組込み関数を追加する方がはるかに簡単で、最適化パスに対して透過的です。追加された機能を関数呼び出しとして表現できる場合、LLVM拡張には組込み関数が最適な方法です。

重要な労力を費やす前に、リストで質問してください。あなたがしようとしていることは、既に存在するインフラストラクチャで行うことができるかどうか、あるいは他の人が既に作業しているかどうかを確認してください。そうすることで、多くの時間と労力を節約できます。

新しい組込み関数の追加

LLVMに新しい組込み関数を追加することは、新しい命令を追加するよりもはるかに簡単です。LLVMへのほとんどすべての拡張は、組込み関数として開始し、必要に応じて命令に変換する必要があります。

  1. llvm/docs/LangRef.html:

    組込み関数を文書化します。それがコードジェネレータ固有のものであるかどうか、そしてどのような制限があるかを決定します。他のメンバーと相談して、それが良いアイデアであることを確認してください。

  2. llvm/include/llvm/IR/Intrinsics*.td:

    組込み関数のためのエントリを追加します。最適化のためのメモリアクセス特性を記述します(これにより、DCE、CSEなどが実行されるかどうかが制御されます)。引数にイミディエートが必要な場合は、ImmArgプロパティで示す必要があります。llvm_any*_ty型のいずれかを引数または戻り値の型として使用する組込み関数は、tblgenによってオーバーロードされたと見なされ、対応するサフィックスが組込み関数の名前に必要になります。

  3. llvm/lib/Analysis/ConstantFolding.cpp:

    組込み関数を定数畳み込みできる場合は、canConstantFoldCallTo関数とConstantFoldCall関数でサポートを追加します。

  4. llvm/test/*:

    テストスイートにテストケースを追加します。

組込み関数がシステムに追加されたら、コードジェネレータのサポートを追加する必要があります。一般的に、次の手順を実行する必要があります。

lib/Target/*/*.tdで、選択したターゲットの.tdファイルにサポートを追加します。

これは通常、組込み関数に一致するパターンを.tdファイルに追加することですが、生成したい命令を追加する必要がある場合もあります。PowerPCとX86バックエンドには、多くの例があります。

新しいSelectionDAGノードの追加

組込み関数と同様に、LLVMに新しいSelectionDAGノードを追加することは、新しい命令を追加するよりもはるかに簡単です。新しいノードは、多くのターゲットで共通の命令を表すために追加されることがよくあります。これらのノードは、多くの場合、LLVM命令(add、sub)または組込み関数(byteswap、population count)にマップされます。他の場合では、新しいノードは、多くのターゲットが共通のタスク(浮動小数点数と整数表現間の変換)を実行できるようにしたり、単一ノードでより複雑な動作をキャプチャしたりするために追加されています(回転)。

  1. include/llvm/CodeGen/ISDOpcodes.h:

    新しいSelectionDAGノードの列挙値を追加します。

  2. lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp:

    getOperationNameにノードを出力するコードを追加します。定数引数が与えられた場合にコンパイル時に評価できる新しいノード(定数と別の定数の加算など)の場合は、適切な数の引数を取るgetNodeメソッドを見つけ、新しいノードと同じ数の引数を取るノードの定数畳み込みを実行するswitch文に、新しいノードのケースを追加します。

  3. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    必要に応じてノードを合法化、昇格、展開するコードを追加します。最低限、オペランドのLegalizeOpを呼び出し、オペランドのいずれかが合法化の結果として変更された場合に新しいノードを返すLegalizeOpのノードのケース文を追加する必要があります。SelectionDAGフレームワークでサポートされているすべてのターゲットが新しいノードをネイティブにサポートするとは限りません。この場合、LegalizeOpのノードのケース文にコードを追加して、新しいノードをより単純で合法的な操作に展開する必要があります。ISD::UREMのケース(剰余を除算、乗算、減算に展開する)は良い例です。

  4. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    ターゲットが特定のサイズでのみ新しいノードの追加をサポートする場合、LegalizeOpのノードのケース文にコードを追加して、ノードのオペランドをより大きなサイズに昇格させ、正しい操作を実行する必要があります。PromoteOpにもコードを追加する必要があります。良い例としてISD::BSWAPがあります。これはオペランドをより大きなサイズに昇格させ、byteswapを実行してから、正しいバイトを右にシフトして、より狭い型でより狭いbyteswapをエミュレートします。

  5. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    ExpandOpにノードのケースを追加して、高半分と低半分に分割された値に対して新しいノードによって表されるアクションを実行する方法を合法化ツールに教えます。このケースは、32ビットターゲットで64ビットオペランドを持つノードをサポートするために使用されます。

  6. lib/CodeGen/SelectionDAG/DAGCombiner.cpp:

    ノードをそれ自体と組み合わせることができる場合、または他の既存のノードをピープホールのような方法で組み合わせることができる場合は、そのノードのvisit関数を追加し、そこからその関数を呼び出します。実行できる単純な組み合わせの例がいくつかあります。visitFABSvisitSRLは良い出発点です。

  7. lib/Target/PowerPC/PPCISelLowering.cpp:

    各ターゲットには、通常は独自のファイル(一部のターゲットではDAGToDAGISelと同じファイルに含まれる場合もあります)にTargetLoweringクラスの実装があります。ターゲットのデフォルトの動作は、新しいノードがそのターゲットに対して合法的なすべての型に対して合法的であると仮定することです。このターゲットが新しいノードをネイティブにサポートしていない場合は、ターゲットに昇格(より大きな型でサポートされている場合)または展開するように指示します。これにより、上記で書いたLegalizeOpのコードが、新しいノードをこのターゲットの他の合法的なノードに分解します。

  8. include/llvm/Target/TargetSelectionDAG.td:

    LLVMでサポートされている現在のほとんどのターゲットは、DAGToDAGメソッドを使用してコードを生成します。このメソッドでは、SelectionDAGノードがターゲット固有のノードにパターンマッチされ、個々の命令を表します。ターゲットが命令を新しいノードにマッチングできるようにするには、このファイルのリストにそのノードのdefを適切な型制約とともに追加する必要があります。addbswapfaddを例として参照してください。

  9. lib/Target/PowerPC/PPCInstrInfo.td:

    各ターゲットには、ターゲットの命令セットを記述するtablegenファイルがあります。DAGToDAG命令選択フレームワークを使用するターゲットの場合、1つ以上のターゲットノードを使用する新しいノードのパターンを追加します。このドキュメントは現在少し不足していますが、いくつかの良い例があります。PPCInstrInfo.tdrotlのパターンを参照してください。

  10. TODO:複雑なパターンの文書化。

  11. llvm/test/CodeGen/*:

    テストスイートに新しいノードのテストケースを追加します。llvm/test/CodeGen/X86/bswap.llが良い例です。

新しい命令の追加

警告

命令を追加すると、ビットコード形式が変更され、以前のバージョンとの互換性を維持するために努力が必要になります。絶対に必要な場合にのみ、命令を追加してください。

  1. llvm/include/llvm/IR/Instruction.def:

    命令の番号と列挙名を追加します。

  2. llvm/include/llvm/IR/Instructions.h:

    命令を表すクラスの定義を追加します。

  3. llvm/include/llvm/IR/InstVisitor.h:

    新しい命令の種類へのビジターのプロトタイプを追加します。

  4. llvm/lib/AsmParser/LLLexer.cpp:

    アセンブリテキストファイルから命令を解析するための新しいトークンを追加します。

  5. llvm/lib/AsmParser/LLParser.cpp:

    命令の読み取り方法と、その結果としてどのような構造が構築されるかを記述する文法を追加します。

  6. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    命令と、ビットコードからどのように解析されるかのケースを追加します。

  7. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    命令と、ビットコードからどのように解析されるかのケースを追加します。

  8. llvm/lib/IR/Instruction.cpp:

    命令をアセンブリに出力する方法のケースを追加します。

  9. llvm/lib/IR/Instructions.cpp:

    llvm/include/llvm/Instructions.hで定義したクラスを実装します。

  10. 命令をテストします。

  11. llvm/lib/Target/*:

    コードジェネレータへの命令のサポートを追加するか、ローワーリングパスを追加します。

  12. llvm/test/*:

    テストスイートにテストケースを追加します。

また、この新しい命令を理解するために必要な分析またはパスを実装(または変更)する必要があります。

新しい型の追加

警告

新しい型を追加すると、ビットコード形式が変更され、現在存在するLLVMインストールとの互換性が失われます。絶対に必要な場合にのみ、新しい型を追加してください。

基本型の追加

  1. llvm/include/llvm/IR/Type.h:

    新しい型に対応する列挙型を追加します。この型用の静的Type*を追加します。

  2. llvm/lib/IR/Type.cppllvm/lib/CodeGen/ValueTypes.cpp

    TypeID から Type*へのマッピングを追加します。静的Type*を初期化します。

  3. llvm/include/llvm-c/Core.hllvm/lib/IR/Core.cpp

    列挙型LLVMTypeKindを追加し、新しい型に対応してLLVMTypeKind LLVMGetTypeKind(LLVMTypeRef Ty)を変更します。

  4. llvm/lib/AsmParser/LLLexer.cpp:

    テキストアセンブリから型を解析する機能を追加します。

  5. llvm/lib/AsmParser/LLParser.cpp:

    その型に対応するトークンを追加します。

  6. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    void ModuleBitcodeWriter::writeTypeTable() を変更して、新しい型をシリアライズします。

  7. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    Error BitcodeReader::parseTypeTableBody() を変更して、新しいデータ型を読み込みます。

  8. include/llvm/Bitcode/LLVMBitCodes.h:

    新しい型に対応する列挙型TypeCodesを追加します。

派生型の追加

  1. llvm/include/llvm/IR/Type.h:

    新しい型に対応する列挙型を追加します。型の前方宣言も追加します。

  2. llvm/include/llvm/IR/DerivedTypes.h:

    階層構造に新しいクラスを表す新しいクラスを追加します。TypeMapの値型への前方宣言を追加します。

  3. llvm/lib/IR/Type.cppllvm/lib/CodeGen/ValueTypes.cpp

    派生型に対するサポートを追加します。特に、enum TypeIDisgetメソッド。

  4. llvm/include/llvm-c/Core.hllvm/lib/IR/Core.cpp

    列挙型LLVMTypeKindを追加し、新しい型に対応してLLVMTypeKind LLVMGetTypeKind(LLVMTypeRef Ty)を変更します。

  5. llvm/lib/AsmParser/LLLexer.cpp:

    lltok::Kind LLLexer::LexIdentifier()を変更して、テキストアセンブリから型を解析する機能を追加します。

  6. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    void ModuleBitcodeWriter::writeTypeTable() を変更して、新しい型をシリアライズします。

  7. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    Error BitcodeReader::parseTypeTableBody() を変更して、新しいデータ型を読み込みます。

  8. include/llvm/Bitcode/LLVMBitCodes.h:

    新しい型に対応する列挙型TypeCodesを追加します。

  9. llvm/lib/IR/AsmWriter.cpp:

    void TypePrinting::print(Type *Ty, raw_ostream &OS)を変更して、新しい派生型を出力します。