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>.tdllvm-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 ターゲットは、複雑な画像およびメモリバッファ命令に関するメタデータを生成するために使用します。

詳細については、検索可能テーブルのリファレンスを参照してください。

CTags

目的: この tablegen バックエンドは、ctags(1) 形式の定義のインデックスを出力します。ヘルパースクリプト `utils/TableGen/tdtags` は、より使いやすいインターフェースを提供します。「tdtags -H」を実行してドキュメントを参照してください。

X86EVEX2VEX

目的: この X86 固有の tablegen バックエンドは、EVEX エンコードされた命令を VEX エンコードされた同一の命令にマップするテーブルを出力します。

Clang バックエンド

ClangAttrClasses

目的: ASTNode = 0 を設定していない Attr.td 内の属性について、セマンティック属性クラスの宣言を含む Attrs.inc を作成します。このファイルは Attr.h の一部としてインクルードされます。

ClangAttrParserStringSwitches

目的: パーサー関連の文字列スイッチ用の StringSwitch::Case ステートメントを含む AttrParserStringSwitches.inc を作成します。各スイッチには、独自の定義済みマクロ (例: CLANG_ATTR_ARG_CONTEXT_LISTCLANG_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.cppAttributeList クラスのいくつかの関数を実装するために使用される 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 静的アナライザーチェッカーを生成します。

ClangCommentHTMLTags

ドキュメントコメントで使用される HTML タグ名の効率的なマッチャーを生成します。

ClangCommentHTMLTagsProperties

HTML タグプロパティの効率的なマッチャーを生成します。

ClangCommentHTMLNamedCharacterReferences

名前付き文字参照を UTF-8 シーケンスに変換する関数を生成します。

ClangCommentCommandInfo

ドキュメントコメントで使用されるコマンドのコマンドプロパティを生成します。

ClangCommentCommandList

ドキュメントコメントで使用されるコマンドのリストを生成します。

ArmNeon

clang 用の arm_neon.h を生成します。

ArmNeonSema

clang 用の ARM NEON sema サポートを生成します。

ArmNeonTest

clang 用の ARM NEON テストを生成します。

AttrDocs

目的: AttrDocs.td から AttributeReference.rst を作成し、ユーザー向けの属性を文書化するために使用されます。

一般的なバックエンド

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 文字列として出力されます。

  • 任意の要素型 Tlist<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 表現では (ここで、Opdef ステートメントで定義された別の場所にあるレコードの名前であると仮定します)

    • 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++イニシャライザーをフォーマットできるように、各テーブルフィールドの型を推測しようとします。bitbits<n>stringIntrinsic、および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に設定すると、ルックアップ関数が変更され、収集されたレコードのプライマリキーの範囲内にあるかどうかを判断するために、プライマリキーの最初のフィールドをテストします。そうでない場合、関数はバイナリ検索を実行せずにヌルポインタを返します。これは、大きな列挙ベースのスペースの一部の要素のみのデータを提供するテーブルに役立ちます。プライマリキーの最初のフィールドは整数型である必要があり、文字列にすることはできません。

上記のATablelet 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];
}