TableGenの概要¶
はじめに¶
TableGenの目的は、人間がドメイン固有の情報の記録を開発および維持するのを支援することです。これらの記録が多数になる可能性があるため、柔軟な記述を可能にし、これらの記録の共通の特徴を分離できるように特別に設計されています。これにより、記述の重複が減り、エラーの可能性が減り、ドメイン固有の情報を構造化しやすくなります。
TableGenのフロントエンドはファイルを解析し、宣言をインスタンス化し、その結果をドメイン固有のバックエンドに渡して処理します。TableGenの詳細な説明については、TableGenプログラマーのリファレンスを参照してください。TableGenのさまざまなフレーバーを実行する*-tblgen
コマンドの詳細については、tblgen - C++コードへの記述を参照してください。
現在、TableGenの主なユーザーは、LLVMのターゲット非依存コードジェネレーターと、Clangの診断と属性です。
TableGenを頻繁に使用し、emacsまたはvimを使用している場合は、LLVMディストリビューションのllvm/utils/emacs
およびllvm/utils/vim
ディレクトリに、それぞれemacsの「TableGenモード」とvimの言語ファイルがあることに注意してください。
TableGenプログラム¶
TableGenファイルは、ビルドディレクトリのbinにあるTableGenプログラム: llvm-tblgenによって解釈されます。LLVMのビルドプロセス以外では使用しないため、システム(またはsysrootが設定されている場所)にはインストールされません。
TableGenの実行¶
TableGenは、他のLLVMツールと同様に実行します。最初の(オプションの)引数は、読み込むファイルを指定します。ファイル名が指定されていない場合、llvm-tblgen
は標準入力から読み込みます。
役立つためには、バックエンドのいずれかを使用する必要があります。これらのバックエンドは、コマンドラインで選択できます(リストについては「llvm-tblgen -help
」と入力してください)。たとえば、特定の型をサブクラス化するすべての定義のリストを取得するには(これらのレコードのenumリストを構築するのに役立ちます)、-print-enums
オプションを使用します。
$ llvm-tblgen X86.td -print-enums -class=Register
AH, AL, AX, BH, BL, BP, BPL, BX, CH, CL, CX, DH, DI, DIL, DL, DX, EAX, EBP, EBX,
ECX, EDI, EDX, EFLAGS, EIP, ESI, ESP, FP0, FP1, FP2, FP3, FP4, FP5, FP6, IP,
MM0, MM1, MM2, MM3, MM4, MM5, MM6, MM7, R10, R10B, R10D, R10W, R11, R11B, R11D,
R11W, R12, R12B, R12D, R12W, R13, R13B, R13D, R13W, R14, R14B, R14D, R14W, R15,
R15B, R15D, R15W, R8, R8B, R8D, R8W, R9, R9B, R9D, R9W, RAX, RBP, RBX, RCX, RDI,
RDX, RIP, RSI, RSP, SI, SIL, SP, SPL, ST0, ST1, ST2, ST3, ST4, ST5, ST6, ST7,
XMM0, XMM1, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15, XMM2, XMM3, XMM4, XMM5,
XMM6, XMM7, XMM8, XMM9,
$ llvm-tblgen X86.td -print-enums -class=Instruction
ABS_F, ABS_Fp32, ABS_Fp64, ABS_Fp80, ADC32mi, ADC32mi8, ADC32mr, ADC32ri,
ADC32ri8, ADC32rm, ADC32rr, ADC64mi32, ADC64mi8, ADC64mr, ADC64ri32, ADC64ri8,
ADC64rm, ADC64rr, ADD16mi, ADD16mi8, ADD16mr, ADD16ri, ADD16ri8, ADD16rm,
ADD16rr, ADD32mi, ADD32mi8, ADD32mr, ADD32ri, ADD32ri8, ADD32rm, ADD32rr,
ADD64mi32, ADD64mi8, ADD64mr, ADD64ri32, ...
デフォルトのバックエンドは、すべてのレコードを出力します。また、-dump-jsonオプションを使用して有効にするJSONデータ構造としてすべてのレコードを出力する一般的なバックエンドもあります。
TableGenを使用する予定の場合は、必要なものに固有の情報を抽出し、適切な方法でフォーマットするバックエンドを作成する必要がある可能性が高くなります。これは、C++でTableGen自体を拡張するか、JSON出力を消費できる任意の言語でスクリプトを作成することで実行できます。
例¶
他の引数がない場合、llvm-tblgenは指定されたファイルを解析し、すべてのクラス、次いで定義のすべてを出力します。これは、さまざまな定義が完全にどのように展開されるかを確認するのに適した方法です。X86.td
ファイルでこれを実行すると、(これを書いている時点では)次のようになります。
...
def ADD32rr { // Instruction X86Inst I
string Namespace = "X86";
dag OutOperandList = (outs GR32:$dst);
dag InOperandList = (ins GR32:$src1, GR32:$src2);
string AsmString = "add{l}\t{$src2, $dst|$dst, $src2}";
list<dag> Pattern = [(set GR32:$dst, (add GR32:$src1, GR32:$src2))];
list<Register> Uses = [];
list<Register> Defs = [EFLAGS];
list<Predicate> Predicates = [];
int CodeSize = 3;
int AddedComplexity = 0;
bit isReturn = 0;
bit isBranch = 0;
bit isIndirectBranch = 0;
bit isBarrier = 0;
bit isCall = 0;
bit canFoldAsLoad = 0;
bit mayLoad = 0;
bit mayStore = 0;
bit isImplicitDef = 0;
bit isConvertibleToThreeAddress = 1;
bit isCommutable = 1;
bit isTerminator = 0;
bit isReMaterializable = 0;
bit isPredicable = 0;
bit hasDelaySlot = 0;
bit usesCustomInserter = 0;
bit hasCtrlDep = 0;
bit isNotDuplicable = 0;
bit hasSideEffects = 0;
InstrItinClass Itinerary = NoItinerary;
string Constraints = "";
string DisableEncoding = "";
bits<8> Opcode = { 0, 0, 0, 0, 0, 0, 0, 1 };
Format Form = MRMDestReg;
bits<6> FormBits = { 0, 0, 0, 0, 1, 1 };
ImmType ImmT = NoImm;
bits<3> ImmTypeBits = { 0, 0, 0 };
bit hasOpSizePrefix = 0;
bit hasAdSizePrefix = 0;
bits<4> Prefix = { 0, 0, 0, 0 };
bit hasREX_WPrefix = 0;
FPFormat FPForm = ?;
bits<3> FPFormBits = { 0, 0, 0 };
}
...
この定義は、x86アーキテクチャの32ビットレジスタ-レジスタadd
命令に対応しています。def ADD32rr
はADD32rr
という名前のレコードを定義し、行末のコメントは定義のスーパークラスを示します。レコードの本文には、TableGenがレコードのためにアセンブルしたすべてのデータが含まれており、命令が「X86」名前空間の一部であること、命令がコードジェネレーターによってどのように選択されるかを示すパターン、それが2アドレス命令であること、特定のエンコードを持つことなどが示されています。レコード内の情報の内容とセマンティクスは、X86バックエンドのニーズに固有であり、例としてのみ示されています。
ご覧のとおり、コードジェネレーターでサポートされているすべての命令に対して多くの情報が必要であり、すべてを手動で指定することは、保守が困難になり、バグが発生しやすく、そもそも面倒です。TableGenを使用しているため、すべての情報は次の定義から導出されました。
let Defs = [EFLAGS],
isCommutable = 1, // X = ADD Y,Z --> X = ADD Z,Y
isConvertibleToThreeAddress = 1 in // Can transform into LEA.
def ADD32rr : I<0x01, MRMDestReg, (outs GR32:$dst),
(ins GR32:$src1, GR32:$src2),
"add{l}\t{$src2, $dst|$dst, $src2}",
[(set GR32:$dst, (add GR32:$src1, GR32:$src2))]>;
この定義では、X86固有のTableGenファイルで定義されているカスタムクラスI
(カスタムクラスX86Inst
から拡張)を使用して、クラスの命令が共有する共通の特徴を分離します。TableGenの重要な機能は、エンドユーザーが情報を記述するときに使用したい抽象化を定義できることです。
構文¶
TableGenには、組み込みの型と仕様を備えた、C++テンプレートに大まかに基づいた構文があります。さらに、TableGenの構文には、multiclass、foreach、letなどの自動化の概念が導入されています。
基本的な概念¶
TableGenファイルは、「クラス」と「定義」の2つの主要な部分で構成されており、どちらも「レコード」と見なされます。
TableGenレコードには、一意の名前、値のリスト、およびスーパークラスのリストがあります。値のリストは、TableGenが各レコードに対して構築する主要なデータです。これは、アプリケーションのドメイン固有の情報を保持します。このデータの解釈は特定のバックエンドに任されていますが、構造とフォーマットルールはTableGenによって処理され、修正されます。
TableGen定義は、「レコード」の具体的な形式です。これらは通常、未定義の値を持たず、「def
」キーワードでマークされます。
def FeatureFPARMv8 : SubtargetFeature<"fp-armv8", "HasFPARMv8", "true",
"Enable ARMv8 FP">;
この例では、FeatureFPARMv8は、いくつかの値で初期化されたSubtargetFeature
レコードです。クラスの名前は、同じファイルまたは他のインクルードされたファイルのいずれかでキーワードclassを介して定義されます。ほとんどのターゲットTableGenファイルには、include/llvm/Target
にある一般的なファイルが含まれています。
TableGenクラスは、他のレコードを構築および記述するために使用される抽象レコードです。これらのクラスを使用すると、エンドユーザーは、対象とするドメイン(LLVMコードジェネレーターの「Register」、「RegisterClass」、「Instruction」など)、またはレコードの共通プロパティを分離するのに役立つ実装者(X86バックエンドの浮動小数点命令を表すために使用される「FPInst」など)のいずれかの抽象化を構築できます。TableGenは、定義の構築に使用されるすべてのクラスを追跡するため、バックエンドは、「Instruction」などの特定のクラスのすべての定義を見つけることができます。
class ProcNoItin<string Name, list<SubtargetFeature> Features>
: Processor<Name, NoItineraries, Features>;
ここで、クラスProcNoItinは、string型のパラメータNameとターゲット機能のリストを受け取り、引数を渡すことによってProcessorクラスを専門化するとともに、NoItinerariesをハードコーディングします。
TableGenマルチクラスは、一度にすべてインスタンス化される抽象レコードのグループです。各インスタンス化により、複数のTableGen定義が生成される可能性があります。マルチクラスが別のマルチクラスから継承する場合、サブマルチクラスの定義は、現在のマルチクラスで宣言されたかのように、現在のマルチクラスの一部になります。
multiclass ro_signed_pats<string T, string Rm, dag Base, dag Offset, dag Extend,
dag address, ValueType sty> {
def : Pat<(i32 (!cast<SDNode>("sextload" # sty) address)),
(!cast<Instruction>("LDRS" # T # "w_" # Rm # "_RegOffset")
Base, Offset, Extend)>;
def : Pat<(i64 (!cast<SDNode>("sextload" # sty) address)),
(!cast<Instruction>("LDRS" # T # "x_" # Rm # "_RegOffset")
Base, Offset, Extend)>;
}
defm : ro_signed_pats<"B", Rm, Base, Offset, Extend,
!foreach(decls.pattern, address,
!subst(SHIFT, imm_eq0, decls.pattern)),
i8>;
TableGenの詳細な説明については、TableGenプログラマーのリファレンスを参照してください。
TableGenバックエンド¶
TableGenファイルは、バックエンドがないと実際には意味がありません。*-tblgen
を実行するときのデフォルトの操作は、情報をテキスト形式で出力することですが、これはTableGenファイル自体をデバッグする場合にのみ役立ちます。TableGenの力は、ただし、ソースファイルを、必要なものに生成できる内部表現に解釈することです。
TableGenの現在の使用法は、テーブルを含む巨大なインクルードファイルを作成することです。これらのファイルは、直接インクルードするか(出力がコーディングしている言語である場合)、またはファイルのインクルードを囲むマクロを介して前処理で使用できます。
バックエンドがC形式でテーブルを既に出力する場合、または出力が単なる文字列のリスト(エラーおよび警告メッセージの場合)である場合は、直接出力を使用できます。同じ情報をさまざまなコンテキスト(命令名など)で使用する必要がある場合は、前処理された出力を使用する必要があります。したがって、バックエンドは、さまざまなコンパイル時形式に形成できるメタ情報リストを出力する必要があります。
利用可能なバックエンドのリストについては、TableGenバックエンドを参照し、新しいバックエンドの作成およびデバッグ方法については、TableGenバックエンド開発者ガイドを参照してください。
ツールとリソース¶
このドキュメントに加えて、TableGenのツールとリソースのリストは、TableGenのREADMEにあります。
TableGenの欠点¶
非常に汎用的であるにもかかわらず、TableGenには何度も指摘されている欠点があります。共通のテーマは、TableGenはドメイン固有言語を構築できるものの、作成される最終的な言語は他のDSLの能力に欠け、その結果、TableGenファイルのサイズと複雑さが大幅に増加するという点です。
同時に、TableGenはカスタムメイドのバックエンドを介して基本概念の事実上あらゆる意味を作成できるため、元の設計が損なわれ、新参者が邪悪なTableGenファイルを理解するのが非常に困難になる可能性があります。
意味論をさらに拡張することを支持する意見もありますが、バックエンドが厳格なルールを遵守することを保証する必要があります。他には、特定の目的のために設計された、より強力で少ないDSLに移行するか、既存のDSLを再利用することを提案しています。