TableGen バックエンド¶
はじめに¶
TableGen バックエンドは、TableGen の機能の中核をなすものです。ソースファイルは、解析され、レコードインスタンスのコレクションとして終わるクラスとレコードを提供しますが、バックエンドがレコードを解釈し、ユーザー(通常は C++ インクルードファイルまたは警告、オプション、エラーメッセージのテキストリスト)にとって意味のある方法で印刷するのはバックエンド次第です。
TableGen は、LLVM、Clang、および MLIR で非常に異なる目標で使用されています。LLVM は、命令、スケジュール、コア、アーキテクチャ機能に関する大量の情報の生成を自動化する方法として使用しています。一部のバックエンドは、複数のソースファイルで使用される出力を生成するため、プリプロセッサのトリックを簡単に使用できるような方法で作成する必要があります。一部のバックエンドは、C++ コード構造をそのまま含めることができるように印刷することもできます。
一方、Clang は、主に診断メッセージ(エラー、警告、ヒント)と属性に使用するため、規模のテキスト側に重点が置かれています。
MLIR は、TableGen を使用して、オペレーション、オペレーションダイアレクト、オペレーショントレイトを定義します。
TableGen の詳細な説明については、TableGen プログラマー向けリファレンスを参照してください。新しいバックエンドの作成に関するガイドについては、TableGen バックエンド開発者向けガイドを参照してください。
LLVM バックエンド¶
警告
この部分は不完全です。以下の各セクションには、3つのサブセクションが必要です。ユーザーのリスト付きの目的の説明、一般的な入力から生成された出力、そして最後に、同様のものがある場合に新しいバックエンドが必要だった理由です。
全体として、各バックエンドは同じ TableGen ファイルタイプを取得し、異なるターゲット/使用法に対して同様の出力に変換します。TableGen ファイル、バックエンド、およびそのユーザーの間には暗黙的な契約があります。
たとえば、グローバルな契約では、各バックエンドはマクロ保護されたセクションを生成します。ファイルがヘッダーまたはソースファイルによってインクルードされているか、または各ファイルのどのコンテキストで使用されているかに基づいて、インクルードする直前にマクロを定義して、正しい出力を取得する必要があります
#define GET_REGINFO_TARGET_DESC
#include "ARMGenRegisterInfo.inc"
そして、生成されたファイルの一部のみが含まれます。これは、同じ情報を複数の形式(インスタンス化、初期化、ゲッター/セッター関数など)で、同じソース TableGen ファイルから、TableGen ファイルを複数回再コンパイルせずに必要な場合に便利です。
場合によっては、複数のマクロが同じインクルードファイルの前に定義されて、複数のブロックを出力することがあります
#define GET_REGISTER_MATCHER
#define GET_SUBTARGET_FEATURE_NAME
#define GET_MATCHER_IMPLEMENTATION
#include "ARMGenAsmMatcher.inc"
マクロは、インクルードファイルで使用されると自動的に undef されます。
すべての LLVM バックエンドでは、ルート TableGen ファイル <Target>.td
で llvm-tblgen
バイナリが実行されます。このファイルには他のすべてのファイルを含める必要があります。これにより、必要なすべての情報にアクセスでき、TableGen ファイルで重複が発生しないことが保証されます。
CodeEmitter¶
目的: CodeEmitterGen は、命令とそのフィールドの説明を使用して、自動コードエミッターを構築します。これは、MachineInstr が与えられたときに命令の(現在のところ、32ビット符号なし)値を返す関数です。
出力: <Target>CodeEmitter::function()
として仮想関数をオーバーライドすることにより、ターゲットの CodeEmitter クラスを実装する C++ コード。
使用法: <Target>MCCodeEmitter.cpp
の最後に直接インクルードするために使用されます。
RegisterInfo¶
目的: この tablegen バックエンドは、コードジェネレーターのターゲットレジスターファイルの説明を出力する役割を担います。この情報は、Register、RegisterAliases、および RegisterClass クラスのインスタンスを使用して収集されます。
出力: レジスターのマッピング、プロパティ、マスクなどを表す列挙型と構造体を持つ C++ コード。
使用法: <Target>BaseRegisterInfo
と <Target>MCTargetDesc
の両方(ヘッダーファイルとソースファイル)で、宣言の問題と初期化の問題のどちらに使用するかを定義するマクロを使用します。
InstrInfo¶
目的: この tablegen バックエンドは、コードジェネレーターのターゲット命令セットの説明を出力する役割を担います。(CodeEmitter との違いは何ですか?)
出力: 命令のマッピング、プロパティ、マスクなどを表す列挙型と構造体を持つ C++ コード。
使用法: <Target>BaseInstrInfo
と <Target>MCTargetDesc
の両方(ヘッダーファイルとソースファイル)で、宣言の問題と初期化の問題のどちらに使用するかを定義するマクロを使用します。
AsmWriter¶
目的: 現在のターゲットのアセンブリプリンターを出力します。
出力: とりわけ、<Target>InstPrinter::printInstruction()
の実装。
使用法: InstPrinter/<Target>InstPrinter.cpp
に直接インクルードされます。
AsmMatcher¶
目的: 解析されたアセンブリオペランドを MCInst 構造体に変換するためのターゲット指定子マッチャーを出力します。また、カスタムオペランド解析のマッチャーも出力します。詳細なドキュメントは、AsmMatcherEmitter.cpp
ファイルに記述されています。
出力: アセンブラーパーサーのマッチャー関数、宣言など。
使用法: バックエンドの AsmParser/<Target>AsmParser.cpp
で、AsmParser クラスを構築するために使用されます。
Disassembler¶
目的: さまざまなアーキテクチャの逆アセンブラーテーブルエミッターが含まれています。詳細なドキュメントは、DisassemblerEmitter.cpp
ファイルに記述されています。
出力: デコードテーブル、静的デコード関数など。
使用法: すべての手作りのデコードの後、すべてのデフォルトデコードに対応するために、Disassembler/<Target>Disassembler.cpp
に直接インクルードされます。
PseudoLowering¶
目的: 擬似命令の低レベル化を生成します。
出力: <Target>AsmPrinter::emitPseudoExpansionLowering()
を実装します。
使用法: <Target>AsmPrinter.cpp
に直接インクルードされます。
CallingConv¶
目的: このターゲットでサポートされる呼び出し規約の説明を出力する役割を担います。
出力: 一致するスタイルでチェーンされた呼び出し規約を処理し、一致しない場合は false を返す静的関数を実装します。
使用法: CC 選択関数によって返される実装への関数ポインターとして、ISelLowering および FastIsel で使用されます。
DAGISel¶
目的: DAG 命令セレクターを生成します。
出力: DAG 選択を自動化するための巨大な関数を作成します。
使用法: SelectionDAGISel
のターゲットの実装内で <Target>ISelDAGToDAG.cpp
にインクルードされます。
DFAPacketizer¶
目的: このクラスは、Schedule.td ファイルを解析し、VLIW アーキテクチャで命令をパケットに追加できるかどうかについて推論するために使用できる API を生成します。このクラスは、命令がパケットに追加されるときに、マシン命令から機能ユニットへの可能なすべてのマッピングをモデル化する決定性有限オートマトン(DFA)を内部的に生成します。
出力: GPU バックエンド(Hexagon、AMD)のスケジューリングテーブル。
使用法: <Target>InstrInfo.cpp
に直接インクルードされます。
FastISel¶
目的: この tablegen バックエンドは、「高速」命令選択アルゴリズムで使用するためのコードを出力します。背景については、lib/CodeGen/SelectionDAG/FastISel.cpp の先頭にあるコメントを参照してください。このファイルは、ターゲットの tablegen 命令情報ファイルをスキャンし、明らかなパターンを持つ命令を抽出し、型と演算子でこれらの命令を検索するコードを出力します。
出力: Predicate
メソッドと FastEmit
メソッドを生成します。
使用法: FastISel
クラスのターゲットの実装のプライベートメソッドを実装します。
サブターゲット¶
目的: サブターゲット列挙を生成します。
出力: サブターゲット情報のための列挙型、グローバル変数、ローカルテーブル。
使用法: <Target>Subtarget
および MCTargetDesc/<Target>MCTargetDesc
ファイル(ヘッダーとソースの両方)を生成します。
組み込み関数¶
目的: (ターゲット) 組み込み関数情報を生成します。
OptParserDefs¶
目的: クラスの列挙値を表示します。
検索可能テーブル¶
目的: カスタム検索可能テーブルを生成します。
出力: 列挙型、グローバルテーブル、および検索ヘルパー関数。
使用法: このバックエンドを使用すると、TableGen レコードから自由形式のターゲット固有のテーブルを生成できます。ARM および AArch64 ターゲットは、このバックエンドを使用してシステムレジスタのテーブルを生成します。AMDGPU ターゲットは、複雑な画像およびメモリバッファ命令に関するメタデータを生成するために使用します。
詳細については、検索可能テーブルのリファレンスを参照してください。
X86EVEX2VEX¶
目的: この X86 固有の tablegen バックエンドは、EVEX エンコードされた命令を VEX エンコードされた同一の命令にマップするテーブルを出力します。
Clang バックエンド¶
ClangAttrClasses¶
目的: ASTNode = 0
を設定していない Attr.td
内の属性について、セマンティック属性クラスの宣言を含む Attrs.inc を作成します。このファイルは Attr.h
の一部としてインクルードされます。
ClangAttrParserStringSwitches¶
目的: パーサー関連の文字列スイッチ用の StringSwitch::Case ステートメントを含む AttrParserStringSwitches.inc を作成します。各スイッチには、独自の定義済みマクロ (例: CLANG_ATTR_ARG_CONTEXT_LIST
、CLANG_ATTR_IDENTIFIER_ARG_LIST
) が与えられ、AttrParserStringSwitches.inc をインクルードする前に定義され、インクルード後に未定義になることが期待されます。
ClangAttrImpl¶
目的: ASTNode = 0
を設定していない Attr.td
内の属性について、セマンティック属性クラスの定義を含む AttrImpl.inc を作成します。このファイルは AttrImpl.cpp
の一部としてインクルードされます。
ClangAttrList¶
目的: セマンティック属性識別子のリストが必要な場合に使用される AttrList.inc を作成します。たとえば、AttrKinds.h
はこのファイルを含め、attr::Kind
列挙値のリストを生成します。このリストは、複数のカテゴリ(属性、継承可能な属性、継承可能なパラメーター属性)に分割されています。この分類は、Attr.td
の情報に基づいて自動的に行われ、dyn_cast
や同様の API に必要な classof
機能を実装するために使用されます。
ClangAttrPCHRead¶
目的: ASTReader::ReadAttributes
関数で属性をデシリアライズするために使用される AttrPCHRead.inc を作成します。
ClangAttrPCHWrite¶
目的: ASTWriter::WriteAttributes
関数で属性をシリアライズするために使用される AttrPCHWrite.inc を作成します。
ClangAttrSpellings¶
目的: __has_attribute
機能テストマクロを実装するために使用される AttrSpellings.inc を作成します。
ClangAttrSpellingListIndex¶
目的: 解析された属性スペリング (どの構文またはスコープが使用されたかを含む) を属性スペリングリストインデックスにマップするために使用される AttrSpellingListIndex.inc を作成します。これらのスペリングリストインデックス値は、AttributeList::getAttributeSpellingListIndex
を介して公開される内部実装の詳細です。
ClangAttrVisitor¶
目的: 再帰的な AST ビジターを実装する場合に使用される AttrVisitor.inc を作成します。
ClangAttrTemplateInstantiate¶
目的: 属性のクローンが必要なテンプレートをインスタンス化するときに使用される instantiateTemplateAttribute
関数を実装する AttrTemplateInstantiate.inc を作成します。
ClangAttrParsedAttrList¶
目的: AttributeList::Kind
解析済み属性列挙を生成するために使用される AttrParsedAttrList.inc を作成します。
ClangAttrParsedAttrImpl¶
目的: AttributeList.cpp
で AttributeList
クラスのいくつかの関数を実装するために使用される AttrParsedAttrImpl.inc を作成します。この機能は、解析済み属性オブジェクトごとに 1 つの要素を含む AttrInfoMap ParsedAttrInfo
配列を介して実装されます。
ClangAttrParsedAttrKinds¶
目的: 文字列 (および構文) を解析済みの属性 AttributeList::Kind
列挙型にマップする AttributeList::getKind
関数を実装するために使用される AttrParsedAttrKinds.inc を作成します。
ClangAttrDump¶
目的: 属性に関する情報をダンプする AttrDump.inc を作成します。ASTDumper::dumpAttr
を実装するために使用されます。
ClangDiagsDefs¶
Clang 診断定義を生成します。
ClangDiagGroups¶
Clang 診断グループを生成します。
ClangDiagsIndexName¶
Clang 診断名インデックスを生成します。
ClangCommentNodes¶
Clang AST コメントノードを生成します。
ClangDeclNodes¶
Clang AST 宣言ノードを生成します。
ClangStmtNodes¶
Clang AST ステートメントノードを生成します。
ClangSACheckers¶
Clang 静的アナライザーチェッカーを生成します。
ClangCommentHTMLNamedCharacterReferences¶
名前付き文字参照を UTF-8 シーケンスに変換する関数を生成します。
ClangCommentCommandInfo¶
ドキュメントコメントで使用されるコマンドのコマンドプロパティを生成します。
ClangCommentCommandList¶
ドキュメントコメントで使用されるコマンドのリストを生成します。
ArmNeon¶
clang 用の arm_neon.h を生成します。
ArmNeonSema¶
clang 用の ARM NEON sema サポートを生成します。
ArmNeonTest¶
clang 用の ARM NEON テストを生成します。
AttrDocs¶
目的: AttrDocs.td
から AttributeReference.rst
を作成し、ユーザー向けの属性を文書化するために使用されます。
一般的なバックエンド¶
レコードの印刷¶
TableGen コマンドオプション --print-records
は、ソースファイルで定義されたすべてのクラスとレコードを印刷するシンプルなバックエンドを呼び出します。これがデフォルトのバックエンドオプションです。詳細については、TableGen バックエンド開発者ガイドを参照してください。
詳細なレコードの印刷¶
TableGen コマンドオプション --print-detailed-records
は、デフォルトのレコードプリンターよりも詳細な、ソースファイルで定義されたすべてのグローバル変数、クラス、およびレコードを印刷するバックエンドを呼び出します。詳細については、TableGen バックエンド開発者ガイドを参照してください。
JSON リファレンス¶
目的: すべての def
内の値を、様々な言語で簡単に解析できる JSON データ構造として出力します。TableGen 自体を変更することなくカスタムバックエンドを作成したり、組み込みバックエンドに渡されるのと同じ TableGen データに対して補助的な解析を実行したりするのに役立ちます。
出力:
出力ファイルのルートは JSON オブジェクト (つまり辞書) であり、以下の固定キーを含みます。
!tablegen_json_version
: このデータの構造に互換性のない変更が加えられた場合に増加する数値バージョンフィールド。ここで説明する形式はバージョン 1 に対応します。!instanceof
: キーが TableGen 入力で定義されたクラス名である辞書。各キーに対応する値は、そのクラスから派生したdef
レコードの名前を示す文字列の配列です。たとえば、root["!instanceof"]["Instruction"]
は、クラスInstruction
から派生したすべてのレコードの名前をリストします。
各 def
レコードについて、ルートオブジェクトはレコード名に対するキーも持ちます。対応する値は、以下の固定キーを含む従属オブジェクトです。
!superclasses
: このレコードが派生するすべてのクラスの名前を示す文字列の配列。!fields
:field
キーワードで定義されたこのレコード内のすべての変数の名前を示す文字列の配列。!name
: レコードの名前を示す文字列。これは常に、このレコードの辞書に対応する JSON ルートオブジェクト内のキーと同一です (レコードが匿名の場合、名前は任意です)。!anonymous
: レコードの名前が TableGen 入力で指定された (これがfalse
の場合) か、TableGen 自体によって作成された (これがtrue
の場合) かを示すブール値。!locs
: このレコードに関連付けられたソースの場所を示す文字列の配列。multiclass
からインスタンス化されたレコードの場合、これは最も内側のmulticlass
から始まり、トップレベルのdefm
で終わる、各def
またはdefm
の場所を示します。各文字列には、ファイル名と行番号がコロンで区切られて含まれています。
レコードで定義された各変数について、そのレコードの def
オブジェクトは、変数名に対するキーも持ちます。対応する値は、以下で説明する規則を使用して、変数の値を JSON に変換したものです。
一部の TableGen データ型は、対応する JSON 型に直接変換されます。
完全に未定義の値 (たとえば、このレコードのいくつかのスーパークラスで初期化子なしで宣言され、レコード自体または他のスーパークラスによって初期化されていない変数など) は、JSON の
null
値として出力されます。int
およびbit
の値は、数値として出力されます。TableGen のint
値は、IEEE 倍精度で正確に表現するには大きすぎる整数を保持できることに注意してください。JSON 出力の整数リテラルは、正確な整数値をすべて表示します。したがって、完全な精度で大きな整数を取得する必要がある場合は、Python の標準json
モジュールなど、そのようなリテラルを精度を失うことなく 64 ビット整数に変換できる JSON リーダーを使用する必要があります。string
およびcode
の値は、JSON 文字列として出力されます。任意の要素型
T
のlist<T>
値は、JSON 配列として出力されます。配列の各要素は、同じ規則を使用して順番に表されます。bits
の値も配列として出力されます。bits
配列は、最下位ビットから最上位ビットの順に並べられます。したがって、インデックスi
の要素は、TableGen ソースでx{i}
として記述されたビットに対応します。ただし、これは、スクリプト言語が TableGen ソースや診断-print-records
出力に表示される順序とは逆の順序で配列を表示する可能性があることを意味することに注意してください。
その他すべての TableGen 値型は、2 つの標準フィールドを含む JSON オブジェクトとして出力されます。kind
は、オブジェクトが表す値の種類を記述する識別子であり、printable
は、-print-records
に表示される値と同じ表現を与える文字列です。
def
オブジェクトへの参照はkind=="def"
を持ち、参照されるオブジェクトの名前を示す追加のフィールドdef
を持ちます。同じレコード内の別の変数への参照は
kind=="var"
を持ち、参照される変数の名前を示す追加のフィールドvar
を持ちます。同じレコード内の
bits
型変数の特定のビットへの参照はkind=="varbit"
を持ち、2 つの追加フィールドを持ちます。var
は参照される変数の名前を示し、index
はビットのインデックスを示します。dag
型の値はkind=="dag"
を持ち、2 つの追加フィールドを持ちます。operator
は dag 初期化子の開始括弧の後の初期値を指定します。args
は、後続の引数を示す配列です。args
の要素は長さ 2 の配列であり、各引数の値とコロン付きの名前 (ある場合) を示します。たとえば、dag 値(Op 22, "hello":$foo)
の JSON 表現では (ここで、Op
はdef
ステートメントで定義された別の場所にあるレコードの名前であると仮定します)operator
は、kind=="def"
およびdef=="Op"
であるオブジェクトになります。args
は配列[[22, null], ["hello", "foo"]]
になります。
その他の種類の値または複雑な式が出力に表示される場合、
kind=="complex"
を持ち、追加のフィールドはありません。これらの値は、バックエンドで必要になるとは想定されていません。必要に応じて、標準のprintable
フィールドを使用して、TableGen ソース構文でそれらの表現を抽出できます。
SearchableTables リファレンス¶
TableGen インクルードファイル SearchableTable.td
は、C++ の検索可能なテーブルを生成するためのクラスを提供します。これらのテーブルについては、以下のセクションで説明します。C++ コードを生成するには、--gen-searchable-tables
オプションを指定して llvm-tblgen
を実行します。これにより、指定したレコードからテーブルを生成するバックエンドが呼び出されます。
検索可能なテーブル用に生成された各データ構造は、#ifdef
によって保護されています。これにより、生成された .inc
ファイルを含め、含める特定のデータ構造のみを選択できます。以下の例は、これらのガードで使用されるマクロ名を示しています。
汎用列挙型¶
GenericEnum
クラスを使用すると、C++ 列挙型と、その型の列挙された要素を簡単に定義できます。型を定義するには、親クラスが GenericEnum
で、名前が目的の列挙型であるレコードを定義します。このクラスは 3 つのフィールドを提供します。これらのフィールドは、let
ステートメントを使用してレコードで設定できます。
string FilterClass
。列挙型には、このクラスから派生する各レコードに対して 1 つの要素があります。これらのレコードは、要素の完全なセットを組み立てるために収集されます。string NameField
。収集されたレコード内の、要素の名前を指定するフィールドの名前。レコードにそのようなフィールドがない場合、レコードの名前が使用されます。string ValueField
。収集されたレコード内の、要素の数値を指定するフィールドの名前。レコードにそのようなフィールドがない場合、整数値が割り当てられます。値は、0 から始まるアルファベット順に割り当てられます。
以下に、要素の値が BEntry
クラスのテンプレート引数として明示的に指定されている例を示します。結果の C++ コードが示されています。
def BValues : GenericEnum {
let FilterClass = "BEntry";
let NameField = "Name";
let ValueField = "Encoding";
}
class BEntry<bits<16> enc> {
string Name = NAME;
bits<16> Encoding = enc;
}
def BFoo : BEntry<0xac>;
def BBar : BEntry<0x14>;
def BZoo : BEntry<0x80>;
def BSnork : BEntry<0x4c>;
#ifdef GET_BValues_DECL
enum BValues {
BBar = 20,
BFoo = 172,
BSnork = 76,
BZoo = 128,
};
#endif
次の例では、要素の値は自動的に割り当てられます。値は、要素名によるアルファベット順に 0 から割り当てられることに注意してください。
def CEnum : GenericEnum {
let FilterClass = "CEnum";
}
class CEnum;
def CFoo : CEnum;
def CBar : CEnum;
def CBaz : CEnum;
#ifdef GET_CEnum_DECL
enum CEnum {
CBar = 0,
CBaz = 1,
CFoo = 2,
};
#endif
汎用テーブル¶
GenericTable
クラスは、検索可能な汎用テーブルを定義するために使用されます。TableGen は、テーブルエントリを定義するための C++ コードを生成し、主キーに基づいてテーブルを検索するための関数の宣言と定義も生成します。テーブルを定義するには、親クラスが GenericTable
で、名前がエントリのグローバルテーブルの名前であるレコードを定義します。このクラスは 6 つのフィールドを提供します。
string FilterClass
。テーブルには、このクラスから派生する各レコードに対して 1 つのエントリがあります。string FilterClassField
。これはFilterClass
のオプションフィールドで、bit
型である必要があります。指定された場合、このフィールドがtrueであるレコードのみが、テーブルに対応するエントリを持ちます。このフィールドがFields
リストに含まれていない場合、生成されたC++フィールドには含まれません。string CppTypeName
。エントリを保持するテーブルのC++の構造体/クラス型名。指定しない場合、FilterClass
の名前が使用されます。list<string> Fields
。テーブルエントリのデータを含む収集されたレコード内のフィールド名のリスト。このリストの順序は、C++イニシャライザーの値の順序を決定します。これらのフィールドの型については、以下を参照してください。list<string> PrimaryKey
。プライマリキーを構成するフィールドのリスト。string PrimaryKeyName
。プライマリキーでルックアップを実行する生成されたC++関数の名前。bit PrimaryKeyEarlyOut
。以下の3番目の例を参照してください。bit PrimaryKeyReturnRange
。1に設定すると、ルックアップ関数の定義を変更して、オブジェクトへの単一ポインタではなく、結果の範囲を返すようにします。この機能は、複数のオブジェクトがルックアップ関数で指定された基準を満たす場合に役立ちます。現在、プライマリのルックアップ関数でのみサポートされています。詳細については、以下の2番目の例を参照してください。
TableGenは、発行されたテーブルでC++イニシャライザーをフォーマットできるように、各テーブルフィールドの型を推測しようとします。bit
、bits<n>
、string
、Intrinsic
、およびInstruction
を推測できます。これらはプライマリキーで使用できます。他のフィールド型は明示的に指定する必要があります。これは以下の2番目の例に示すように行われます。このようなフィールドはプライマリキーで使用できません。
フィールド型の特別なケースの1つは、コードに関するものです。任意のコードは文字列で表現されますが、引用符なしでC++イニシャライザーとして発行する必要があります。コードフィールドがコードリテラル([{...}]
)を使用して定義された場合、TableGenは引用符なしで発行することを認識します。ただし、文字列リテラルまたは複雑な文字列式を使用して定義された場合、TableGenは認識しません。この場合、GenericTable
レコードに次の行を含めることで、TableGenにフィールドをコードとして扱うように強制できます。ここで、xxxはコードフィールド名です。
string TypeOf_xxx = "code";
これはTableGenがフィールド型を推測できる例です。テーブルエントリレコードは匿名であることに注意してください。エントリレコードの名前は無関係です。
def ATable : GenericTable {
let FilterClass = "AEntry";
let FilterClassField = "IsNeeded";
let Fields = ["Str", "Val1", "Val2"];
let PrimaryKey = ["Val1", "Val2"];
let PrimaryKeyName = "lookupATableByValues";
}
class AEntry<string str, int val1, int val2, bit isNeeded> {
string Str = str;
bits<8> Val1 = val1;
bits<10> Val2 = val2;
bit IsNeeded = isNeeded;
}
def : AEntry<"Bob", 5, 3, 1>;
def : AEntry<"Carol", 2, 6, 1>;
def : AEntry<"Ted", 4, 4, 1>;
def : AEntry<"Alice", 4, 5, 1>;
def : AEntry<"Costa", 2, 1, 1>;
def : AEntry<"Dale", 2, 1, 0>;
これが生成されたC++コードです。lookupATableByValues
の宣言はGET_ATable_DECL
で保護されており、定義はGET_ATable_IMPL
で保護されています。
#ifdef GET_ATable_DECL
const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2);
#endif
#ifdef GET_ATable_IMPL
constexpr AEntry ATable[] = {
{ "Costa", 0x2, 0x1 }, // 0
{ "Carol", 0x2, 0x6 }, // 1
{ "Ted", 0x4, 0x4 }, // 2
{ "Alice", 0x4, 0x5 }, // 3
{ "Bob", 0x5, 0x3 }, // 4
/* { "Dale", 0x2, 0x1 }, // 5 */ // We don't generate this line as `IsNeeded` is 0.
};
const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2) {
struct KeyType {
uint8_t Val1;
uint16_t Val2;
};
KeyType Key = { Val1, Val2 };
auto Table = ArrayRef(ATable);
auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
[](const AEntry &LHS, const KeyType &RHS) {
if (LHS.Val1 < RHS.Val1)
return true;
if (LHS.Val1 > RHS.Val1)
return false;
if (LHS.Val2 < RHS.Val2)
return true;
if (LHS.Val2 > RHS.Val2)
return false;
return false;
});
if (Idx == Table.end() ||
Key.Val1 != Idx->Val1 ||
Key.Val2 != Idx->Val2)
return nullptr;
return &*Idx;
}
#endif
ATable
のテーブルエントリは、Val1
で順にソートされ、それらの各値内でVal2
でソートされます。これにより、テーブルのバイナリ検索が可能になり、ルックアップ関数でstd::lower_bound
によって実行されます。ルックアップ関数は、見つかったテーブルエントリへの参照、またはエントリが見つからない場合はヌルポインタを返します。テーブルに、整数型で密に番号付けされた単一のプライマリキーフィールドがある場合、バイナリ検索ではなく直接ルックアップが生成されます。
この例には、TableGenが型を推測できないフィールドが含まれています。Kind
フィールドは、上記で定義された列挙型CEnum
を使用しています。型をTableGenに通知するために、GenericTable
から派生したレコードには、TypeOf_
fieldという名前の文字列フィールドを含める必要があります。ここで、fieldは型が必要なフィールドの名前です。
def CTable : GenericTable {
let FilterClass = "CEntry";
let Fields = ["Name", "Kind", "Encoding"];
string TypeOf_Kind = "CEnum";
let PrimaryKey = ["Encoding"];
let PrimaryKeyName = "lookupCEntryByEncoding";
}
class CEntry<string name, CEnum kind, int enc> {
string Name = name;
CEnum Kind = kind;
bits<16> Encoding = enc;
}
def : CEntry<"Apple", CFoo, 10>;
def : CEntry<"Pear", CBaz, 15>;
def : CEntry<"Apple", CBar, 13>;
これが生成されたC++コードです。
#ifdef GET_CTable_DECL
const CEntry *lookupCEntryByEncoding(uint16_t Encoding);
#endif
#ifdef GET_CTable_IMPL
constexpr CEntry CTable[] = {
{ "Apple", CFoo, 0xA }, // 0
{ "Apple", CBar, 0xD }, // 1
{ "Pear", CBaz, 0xF }, // 2
};
const CEntry *lookupCEntryByEncoding(uint16_t Encoding) {
struct KeyType {
uint16_t Encoding;
};
KeyType Key = { Encoding };
auto Table = ArrayRef(CTable);
auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
[](const CEntry &LHS, const KeyType &RHS) {
if (LHS.Encoding < RHS.Encoding)
return true;
if (LHS.Encoding > RHS.Encoding)
return false;
return false;
});
if (Idx == Table.end() ||
Key.Encoding != Idx->Encoding)
return nullptr;
return &*Idx;
}
上記の例で、レコードCEntry<"Pear", CBaz, 15>
と同じエンコードを持つレコードをもう1つ追加しましょう。
def CFoobar : CEnum;
def : CEntry<"Banana", CFoobar, 15>;
以下は、新しく生成されたCTable
です。
#ifdef GET_Table_IMPL
constexpr CEntry Table[] = {
{ "Apple", CFoo, 0xA }, // 0
{ "Apple", CBar, 0xD }, // 1
{ "Banana", CFoobar, 0xF }, // 2
{ "Pear", CBaz, 0xF }, // 3
};
Banana
は辞書式順序で最初に表示されるため、CEntry
テーブルでは、Banana
という名前のレコードはPear
という名前のレコードよりも前に来ます。このため、lookupCEntryByEncoding
関数は、場合によっては正しい結果がPear
という名前のレコードであっても、常にBanana
という名前のレコードへのポインタを返します。このようなシナリオでは、既存のルックアップ関数は常にテーブルから単一のエントリへのポインタを返すため、不十分になります。代わりに、複数のエントリがルックアップ関数で探している基準に一致するため、結果の範囲を返す必要があります。この場合、ルックアップ関数の定義を、PrimaryKeyReturnRange
を設定することで結果の範囲を返すように変更する必要があります。
def CTable : GenericTable {
let FilterClass = "CEntry";
let Fields = ["Name", "Kind", "Encoding"];
string TypeOf_Kind = "CEnum";
let PrimaryKey = ["Encoding"];
let PrimaryKeyName = "lookupCEntryByEncoding";
let PrimaryKeyReturnRange = true;
}
これが変更されたルックアップ関数です。
llvm::iterator_range<const CEntry *> lookupCEntryByEncoding(uint16_t Encoding) {
struct KeyType {
uint16_t Encoding;
};
KeyType Key = {Encoding};
struct Comp {
bool operator()(const CEntry &LHS, const KeyType &RHS) const {
if (LHS.Encoding < RHS.Encoding)
return true;
if (LHS.Encoding > RHS.Encoding)
return false;
return false;
}
bool operator()(const KeyType &LHS, const CEntry &RHS) const {
if (LHS.Encoding < RHS.Encoding)
return true;
if (LHS.Encoding > RHS.Encoding)
return false;
return false;
}
};
auto Table = ArrayRef(Table);
auto It = std::equal_range(Table.begin(), Table.end(), Key, Comp());
return llvm::make_range(It.first, It.second);
}
新しいルックアップ関数は、テーブルからの最初の一致結果への最初のポインタと、最後の一致結果への最後のポインタを持つイテレータ範囲を返します。ただし、変更された定義の発行のサポートはPrimaryKeyName
のみに存在することに注意してください。
PrimaryKeyEarlyOut
フィールドを1に設定すると、ルックアップ関数が変更され、収集されたレコードのプライマリキーの範囲内にあるかどうかを判断するために、プライマリキーの最初のフィールドをテストします。そうでない場合、関数はバイナリ検索を実行せずにヌルポインタを返します。これは、大きな列挙ベースのスペースの一部の要素のみのデータを提供するテーブルに役立ちます。プライマリキーの最初のフィールドは整数型である必要があり、文字列にすることはできません。
上記のATable
にlet PrimaryKeyEarlyOut = 1
を追加すると
def ATable : GenericTable {
let FilterClass = "AEntry";
let Fields = ["Str", "Val1", "Val2"];
let PrimaryKey = ["Val1", "Val2"];
let PrimaryKeyName = "lookupATableByValues";
let PrimaryKeyEarlyOut = 1;
}
ルックアップ関数が次のように変更されます。
const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2) {
if ((Val1 < 0x2) ||
(Val1 > 0x5))
return nullptr;
struct KeyType {
...
同じFilterClass
を持つ2つのGenericTableを構築して、同じレコードの全体セットから選択できますが、異なるFilterClassField
値を割り当てることで、そのクラスのレコードの異なるサブセットを含めることができます。
たとえば、偶数または奇数のレコードのみを含む2つのテーブルを作成できます。IsEven
フィールドとIsOdd
フィールドは、Fields
リストに含まれていないため、生成されたC++フィールドには含まれません。
class EEntry<bits<8> value> {
bits<8> Value = value;
bit IsEven = !eq(!and(value, 1), 0);
bit IsOdd = !not(IsEven);
}
foreach i = {1-10} in {
def : EEntry<i>;
}
def EEntryEvenTable : GenericTable {
let FilterClass = "EEntry";
let FilterClassField = "IsEven";
let Fields = ["Value"];
let PrimaryKey = ["Value"];
let PrimaryKeyName = "lookupEEntryEvenTableByValue";
}
def EEntryOddTable : GenericTable {
let FilterClass = "EEntry";
let FilterClassField = "IsOdd";
let Fields = ["Value"];
let PrimaryKey = ["Value"];
let PrimaryKeyName = "lookupEEntryOddTableByValue";
}
生成されたテーブルは次のとおりです。
constexpr EEntry EEntryEvenTable[] = {
{ 0x2 }, // 0
{ 0x4 }, // 1
{ 0x6 }, // 2
{ 0x8 }, // 3
{ 0xA }, // 4
};
constexpr EEntry EEntryOddTable[] = {
{ 0x1 }, // 0
{ 0x3 }, // 1
{ 0x5 }, // 2
{ 0x7 }, // 3
{ 0x9 }, // 4
};
検索インデックス¶
SearchIndex
クラスは、ジェネリックテーブルの追加のルックアップ関数を定義するために使用されます。追加の関数を定義するには、親クラスがSearchIndex
であり、名前が目的のルックアップ関数の名前であるレコードを定義します。このクラスには3つのフィールドがあります。
GenericTable Table
。別のルックアップ関数を受け取るテーブルの名前。list<string> Key
。セカンダリキーを構成するフィールドのリスト。bit EarlyOut
。ジェネリックテーブルの3番目の例を参照してください。bit ReturnRange
。ジェネリックテーブルの2番目の例を参照してください。
これは、上記のCTable
に追加されたセカンダリキーの例です。生成された関数は、Name
フィールドとKind
フィールドに基づいてエントリをルックアップします。
def lookupCEntry : SearchIndex {
let Table = CTable;
let Key = ["Name", "Kind"];
}
このSearchIndex
の使用により、次の追加のC++コードが生成されます。
const CEntry *lookupCEntry(StringRef Name, unsigned Kind);
...
const CEntry *lookupCEntryByName(StringRef Name, unsigned Kind) {
struct IndexType {
const char * Name;
unsigned Kind;
unsigned _index;
};
static const struct IndexType Index[] = {
{ "APPLE", CBar, 1 },
{ "APPLE", CFoo, 0 },
{ "PEAR", CBaz, 2 },
};
struct KeyType {
std::string Name;
unsigned Kind;
};
KeyType Key = { Name.upper(), Kind };
auto Table = ArrayRef(Index);
auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
[](const IndexType &LHS, const KeyType &RHS) {
int CmpName = StringRef(LHS.Name).compare(RHS.Name);
if (CmpName < 0) return true;
if (CmpName > 0) return false;
if ((unsigned)LHS.Kind < (unsigned)RHS.Kind)
return true;
if ((unsigned)LHS.Kind > (unsigned)RHS.Kind)
return false;
return false;
});
if (Idx == Table.end() ||
Key.Name != Idx->Name ||
Key.Kind != Idx->Kind)
return nullptr;
return &CTable[Idx->_index];
}