1 TableGen プログラマーリファレンス

1.1 はじめに

TableGen の目的は、出力ファイルよりもはるかに簡単にコーディングでき、時間経過に伴う保守や変更も容易なソースファイルからの情報に基づいて、複雑な出力ファイルを生成することです。情報はクラスとレコードを含む宣言的なスタイルでコーディングされ、TableGen によって処理されます。内部化されたレコードはさまざまなバックエンドに渡され、バックエンドはレコードのサブセットから情報を抽出し、1つ以上の出力ファイルを生成します。これらの出力ファイルは通常、C++ の .inc ファイルですが、バックエンドの開発者が必要とする任意のタイプのファイルにすることができます。

このドキュメントでは、LLVM TableGen 機能について詳細に説明します。これは、TableGen を使用してプロジェクトのコードを生成するプログラマーを対象としています。簡単な概要をお探しの場合は、TableGen 概要を確認してください。TableGen を呼び出すために使用されるさまざまな *-tblgen コマンドについては、tblgen ファミリー - C++ コードへの記述で説明しています。

バックエンドの例としては、特定のターゲットマシンのレジスタファイル情報を生成し、LLVM のターゲット非依存コードジェネレーターで使用する RegisterInfo があります。LLVM TableGen バックエンドの説明については、TableGen バックエンドを参照し、新しいバックエンドの作成に関するガイドについては、TableGen バックエンド開発者ガイドを参照してください。

バックエンドができることのいくつかを次に示します。

  • 特定のターゲットマシンのレジスタファイル情報を生成します。

  • ターゲットの命令定義を生成します。

  • コードジェネレーターが命令を中間表現 (IR) ノードに一致させるために使用するパターンを生成します。

  • Clang のセマンティック属性識別子を生成します。

  • Clang の抽象構文木 (AST) 宣言ノード定義を生成します。

  • Clang の AST ステートメントノード定義を生成します。

1.1.1 概念

TableGen ソースファイルには、主に 2 つの項目が含まれています。抽象レコード具体的なレコードです。このドキュメントおよびその他の TableGen ドキュメントでは、抽象レコードはクラスと呼ばれます。(これらのクラスは C++ クラスとは異なり、C++ クラスにはマップされません。)さらに、具体的なレコードは通常はレコードと呼ばれますが、レコードという用語はクラスと具体的なレコードの両方を指す場合があります。区別は文脈上明確である必要があります。

クラスと具体的なレコードには、プログラマーが選択するか、TableGen によって生成された一意の名前があります。その名前には、値を持つフィールドのリストと、オプションで親クラス(ベースクラスまたはスーパークラスと呼ばれることもあります)のリストが関連付けられています。フィールドは、バックエンドが処理する主要なデータです。TableGen はフィールドに意味を割り当てないことに注意してください。意味は、バックエンドとそれらのバックエンドの出力を組み込むプログラムに完全に依存します。

注意

「親クラス」という用語は、別のクラスの親であるクラスと、具体的なレコードが継承するクラスの両方を指すことがあります。この非標準的な用語の使用は、TableGen がクラスと具体的なレコードを同様に扱うために生じます。

バックエンドは、TableGen パーサーによって構築された具体的なレコードのサブセットを処理し、出力ファイルを生成します。これらのファイルは通常、これらのレコードのデータを必要とするプログラムによってインクルードされる C++ の .inc ファイルです。ただし、バックエンドは任意のタイプの出力ファイルを生成できます。たとえば、識別子と置換パラメーターでタグ付けされたメッセージを含むデータファイルを生成できます。LLVM コードジェネレーターなどの複雑なユースケースでは、多くの具体的なレコードがあり、その一部に予期せず多数のフィールドが含まれる可能性があり、結果として大きな出力ファイルになる可能性があります。

TableGen ファイルの複雑さを軽減するために、クラスはレコードフィールドのグループを抽象化するために使用されます。たとえば、いくつかのクラスがマシンレジスタファイルの概念を抽象化し、他のクラスが命令形式を抽象化し、さらに他のクラスが個々の命令を抽象化する場合があります。TableGen では、任意のクラスの階層が許可されるため、2 つの概念の抽象クラスは、元の 2 つの概念から共通の「サブ概念」を抽象化する 3 番目のスーパークラスを共有できます。

クラスをより有用にするために、具体的なレコード(または別のクラス)は親クラスとしてクラスを要求し、テンプレート引数を渡すことができます。これらのテンプレート引数は、親クラスのフィールドで使用して、カスタムの方法で初期化できます。つまり、レコードまたはクラス A は、1 組のテンプレート引数を使用して親クラス S を要求でき、レコードまたはクラス B は、異なる引数セットを使用して S を要求できます。テンプレート引数がない場合、テンプレート引数の組み合わせごとに多くのクラスが必要になります。

クラスと具体的なレコードの両方に、初期化されていないフィールドを含めることができます。初期化されていない「値」は疑問符(?)で表されます。クラスには、具体的なレコードによって継承されたときに記入されることが期待される初期化されていないフィールドがよくあります。それでも、具体的なレコードの一部のフィールドは初期化されていない可能性があります。

TableGen は、レコード定義のグループを 1 か所に収集するためにマルチクラスを提供します。マルチクラスは、複数の具体的なレコードを一度に定義するために「呼び出す」ことができる一種のマクロです。マルチクラスは他のマルチクラスから継承できるため、マルチクラスはその親マルチクラスからすべての定義を継承します。

付録 C:サンプルレコードでは、Intel X86 ターゲットの複雑なレコードと、その定義方法の単純さを示しています。

1.2 ソースファイル

TableGen ソースファイルは、プレーン ASCII テキストファイルです。ファイルには、ステートメント、コメント、および空白行を含めることができます(字句解析を参照)。TableGen ファイルの標準ファイル拡張子は .td です。

TableGen ファイルは非常に大きくなる可能性があるため、あるファイルが別のファイルの内容を含めることを許可するインクルードメカニズムがあります(インクルードファイルを参照)。これにより、大きなファイルを小さなファイルに分割したり、複数のソースファイルが同じライブラリファイルを含めることができるシンプルなライブラリメカニズムを提供したりできます。

TableGen は、.td ファイルの一部を条件付きにするために使用できるシンプルなプリプロセッサーをサポートしています。詳細については、プリプロセッシング機能を参照してください。

1.3 字句解析

ここで使用されている字句および構文表記法は、Python の表記法を模倣することを目的としています。特に、字句定義の場合、生成は文字レベルで動作し、要素間に暗黙の空白はありません。構文定義はトークンレベルで動作するため、トークン間に暗黙の空白があります。

TableGenは、BCPLスタイルのコメント(// ...)と、ネスト可能なCスタイルのコメント(/* ... */)をサポートしています。TableGenは、簡単なプリプロセッシング機能も提供します。

改ページ文字は、レビューのためにファイルを印刷する際に改ページを生成するために、ファイル内で自由に使用できます。

以下は、基本的な句読点トークンです。

- + [ ] { } ( ) < > : ; . ... = ? #

1.3.1 リテラル

数値リテラルは、次のいずれかの形式を取ります。

TokInteger     ::=  DecimalInteger | HexInteger | BinInteger
DecimalInteger ::=  ["+" | "-"] ("0"..."9")+
HexInteger     ::=  "0x" ("0"..."9" | "a"..."f" | "A"..."F")+
BinInteger     ::=  "0b" ("0" | "1")+

DecimalInteger トークンには、ほとんどの言語とは異なり、オプションの + または - 符号が含まれていることに注意してください。ほとんどの言語では、符号は単項演算子として扱われます。

TableGenには、2種類の文字列リテラルがあります。

TokString ::=  '"' (non-'"' characters and escapes) '"'
TokCode   ::=  "[{" (text not containing "}]") "}]"

TokCode は、[{}] で区切られた複数行の文字列リテラルにすぎません。複数行にまたがることができ、改行は文字列内に保持されます。

現在の実装では、以下のエスケープシーケンスが受け入れられます。

\\ \' \" \t \n

1.3.2 識別子

TableGenには、名前のようなトークンと識別子のようなトークンがあり、大文字と小文字が区別されます。

ualpha        ::=  "a"..."z" | "A"..."Z" | "_"
TokIdentifier ::=  ("0"..."9")* ualpha (ualpha | "0"..."9")*
TokVarName    ::=  "$" ualpha (ualpha |  "0"..."9")*

ほとんどの言語とは異なり、TableGenでは、TokIdentifier が整数で始まることを許可していることに注意してください。あいまいな場合は、トークンは識別子ではなく数値リテラルとして解釈されます。

TableGenには、以下の予約済みキーワードがあり、識別子として使用することはできません。

assert     bit           bits          class         code
dag        def           dump          else          false
foreach    defm          defset        defvar        field
if         in            include       int           let
list       multiclass    string        then          true

警告

field 予約語は、CodeEmitterGenバックエンドで使用される場合を除き、非推奨です。CodeEmitterGenバックエンドでは、通常のレコードフィールドとエンコードフィールドを区別するために使用されます。

1.3.3 Bang演算子

TableGenは、さまざまな用途に使用できる「bang演算子」を提供します。

BangOperator ::=  one of
                  !add         !and         !cast        !con         !dag
                  !div         !empty       !eq          !exists      !filter
                  !find        !foldl       !foreach     !ge          !getdagarg
                  !getdagname  !getdagop    !gt          !head        !if
                  !interleave  !isa         !le          !listconcat  !listremove
                  !listsplat   !logtwo      !lt          !mul         !ne
                  !not         !or          !range       !repr        !setdagarg
                  !setdagname  !setdagop    !shl         !size        !sra
                  !srl         !strconcat   !sub         !subst       !substr
                  !tail        !tolower     !toupper     !xor

!cond 演算子は、他のbang演算子と比較してわずかに異なる構文を持っているため、別々に定義されています。

CondOperator ::=  !cond

各bang演算子の説明については、付録A: Bang演算子を参照してください。

1.3.4 インクルードファイル

TableGenにはインクルードメカニズムがあります。インクルードされたファイルの内容は、include ディレクティブを字句的に置き換え、あたかも元のメインファイルにあったかのように解析されます。

IncludeDirective ::=  "include" TokString

メインファイルとインクルードファイルの一部は、プリプロセッサディレクティブを使用して条件化できます。

PreprocessorDirective ::=  "#define" | "#ifdef" | "#ifndef"

1.4

TableGen言語は、シンプルかつ完全な型システムを使用する静的型付け言語です。型は、エラーをチェックし、暗黙的な変換を実行し、インターフェイス設計者が許可された入力を制約するのに役立ちます。すべての値には、関連付けられた型が必要です。

TableGenは、低レベル型(例:bit)と高レベル型(例:dag)の混合をサポートしています。この柔軟性により、幅広いレコードを便利かつコンパクトに記述できます。

Type    ::=  "bit" | "int" | "string" | "dag"
            | "bits" "<" TokInteger ">"
            | "list" "<" Type ">"
            | ClassID
ClassID ::=  TokIdentifier
bit

bit は、0または1のブール値です。

int

int 型は、5や-42などの単純な64ビット整数値を表します。

string

string 型は、任意の長さの文字の順序付きシーケンスを表します。

bits<n>

bits 型は、個別のビットとして扱われる、任意の長さ *n* の固定サイズの整数です。これらのビットには個別にアクセスできます。この型のフィールドは、命令オペコード、レジスタ番号、またはアドレッシングモード/レジスタ/ディスプレースメントを表すのに役立ちます。フィールドのビットは、個別にまたはサブフィールドとして設定できます。たとえば、命令アドレスでは、アドレッシングモード、ベースレジスタ番号、およびディスプレースメントを個別に設定できます。

list<type>

この型は、山かっこで指定された *type* の要素を持つリストを表します。要素の型は任意であり、別のリスト型であってもかまいません。リスト要素は0からインデックス付けされます。

dag

この型は、ノードのネスト可能な有向非巡回グラフ(DAG)を表します。各ノードには、*演算子*と0個以上の*引数*(または*オペランド*)があります。引数は、別の dag オブジェクトにすることができ、ノードとエッジの任意のツリーを可能にします。例として、DAGは、コード生成器の命令選択アルゴリズムで使用するためのコードパターンを表すために使用されます。詳細については、有向非巡回グラフ(DAG)を参照してください。

ClassID

型のコンテキストでクラス名を指定すると、定義された値の型は、指定されたクラスのサブクラスである必要があります。これは、list 型と組み合わせて使用すると便利です。たとえば、リストの要素を共通のベースクラスに制約するために使用できます(例:list<Register> は、Register クラスから派生した定義のみを含めることができます)。ClassID は、以前に宣言または定義されたクラスの名前である必要があります。

1.5 値と式

TableGenステートメントには、値が必要なコンテキストが多数あります。一般的な例は、レコードの定義です。各フィールドは、名前とオプションの値で指定されます。TableGenでは、値式を構築するときに、妥当な数の異なる形式を使用できます。これらの形式を使用すると、TableGenファイルをアプリケーションにとって自然な構文で記述できます。

すべての値には、ある型から別の型に変換するためのルールがあることに注意してください。たとえば、これらのルールを使用すると、7 のような値を bits<4> 型のエンティティに割り当てることができます。

Value         ::=  SimpleValue ValueSuffix*
                  | Value "#" [Value]
ValueSuffix   ::=  "{" RangeList "}"
                  | "[" SliceElements "]"
                  | "." TokIdentifier
RangeList     ::=  RangePiece ("," RangePiece)*
RangePiece    ::=  TokInteger
                  | TokInteger "..." TokInteger
                  | TokInteger "-" TokInteger
                  | TokInteger TokInteger
SliceElements ::=  (SliceElement ",")* SliceElement ","?
SliceElement  ::=  Value
                  | Value "..." Value
                  | Value "-" Value
                  | Value TokInteger

警告

RangePieceSliceElement の特殊な最後の形式は、「-」が TokInteger に含まれているため、1-5 が "1"と "-5"の値を持つ2つの連続したトークンとして字句解析されるためであり、"1", "-", および "5"として解析されるわけではないからです。範囲の句読点としてのハイフンの使用は非推奨です。

1.5.1 単純な値

SimpleValue には、いくつかの形式があります。

SimpleValue ::=  TokInteger | TokString+ | TokCode

値は、整数リテラル、文字列リテラル、またはコードリテラルにすることができます。複数の隣接する文字列リテラルは、C/C++のように連結されます。単純な値は、文字列の連結です。コードリテラルは文字列になり、それらと区別できなくなります。

SimpleValue2 ::=  "true" | "false"

truefalse リテラルは、本質的に整数値1と0の構文糖衣です。これらは、フィールドの初期化、ビットシーケンス、if ステートメントなどでブール値が使用されている場合、TableGenファイルの可読性を向上させます。解析されると、これらのリテラルは整数に変換されます。

注意

truefalse は1と0のリテラル名ですが、スタイルのルールとして、ブール値にのみ使用することをお勧めします。

SimpleValue3 ::=  "?"

疑問符は、初期化されていない値を表します。

SimpleValue4 ::=  "{" [ValueList] "}"
ValueList    ::=  ValueListNE
ValueListNE  ::=  Value ("," Value)*

この値は、bits<n> フィールドを初期化するために使用できるビットのシーケンスを表します(中かっこに注意してください)。そうする場合、値は合計 *n* ビットを表す必要があります。

SimpleValue5 ::=  "[" ValueList "]" ["<" Type ">"]

この値はリスト初期化子です(角括弧に注目してください)。角括弧内の値はリストの要素です。オプションのTypeを使用して、特定の要素型を示すことができます。それ以外の場合、要素型は指定された値から推論されます。TableGenは通常、型を推論できますが、値が空のリスト([])の場合は推論できないことがあります。

SimpleValue6 ::=  "(" DagArg [DagArgList] ")"
DagArgList   ::=  DagArg ("," DagArg)*
DagArg       ::=  Value [":" TokVarName] | TokVarName

これはDAG初期化子を表します(丸括弧に注目してください)。最初のDagArgはDAGの「演算子」と呼ばれ、レコードである必要があります。詳細については、有向非巡回グラフ(DAG)を参照してください。

SimpleValue7 ::=  TokIdentifier

結果の値は、識別子で指定されたエンティティの値です。可能な識別子についてはここで説明しますが、このガイドの残りの部分を読むと、説明がより理解しやすくなるでしょう。

  • classのテンプレート引数。例えば、

    Barの使用など。

    class Foo <int Bar> {
      int Baz = Bar;
    }
    
  • classまたはmulticlass定義における暗黙的なテンプレート引数NAMENAMEを参照)。

  • classにローカルなフィールド。例えば、

    Barの使用など。

    class Foo {
      int Bar = 5;
      int Baz = Bar;
    }
    
  • レコード定義の名前。例えば、Fooの定義におけるBarの使用など。

    def Bar : SomeClass {
      int X = 5;
    }
    
    def Foo {
      SomeClass Baz = Bar;
    }
    
  • レコード定義にローカルなフィールド。例えば、

    Barの使用など。

    def Foo {
      int Bar = 5;
      int Baz = Bar;
    }
    

    レコードの親クラスから継承されたフィールドには、同じ方法でアクセスできます。

  • multiclassのテンプレート引数。例えば、

    Barの使用など。

    multiclass Foo <int Bar> {
      def : SomeClass<Bar>;
    }
    
  • defvarまたはdefsetステートメントで定義された変数。

  • foreachの反復変数。例えば、

    iの使用など。

    foreach i = 0...5 in
      def Foo#i;
    
SimpleValue8 ::=  ClassID "<" ArgValueList ">"

この形式は、新しい匿名レコード定義を作成します(与えられたクラスから与えられたテンプレート引数で継承する名前のないdefによって作成されるのと同様です。 defを参照してください)。値はそのレコードです。レコードのフィールドは、接尾辞を使用して取得できます。 接尾辞付きの値を参照してください。

この方法でクラスを呼び出すと、単純なサブルーチン機能を提供できます。詳細については、クラスをサブルーチンとして使用するを参照してください。

SimpleValue9 ::=  BangOperator ["<" Type ">"] "(" ValueListNE ")"
                 | CondOperator "(" CondClause ("," CondClause)* ")"
CondClause   ::=  Value ":" Value

感嘆符演算子は、他の単純な値では利用できない関数を提供します。!condの場合を除き、感嘆符演算子は丸括弧で囲まれた引数のリストを取り、これらの引数に対して何らかの関数を実行し、その感嘆符演算子の値を生成します。!cond演算子は、コロンで区切られた引数のペアのリストを取ります。各感嘆符演算子の説明については、付録A:感嘆符演算子を参照してください。

1.5.2 接尾辞付きの値

上記で説明したSimpleValueの値は、特定の接尾辞で指定できます。接尾辞の目的は、プライマリ値のサブ値を取得することです。以下に、プライマリのに対する可能な接尾辞を示します。

value{17}

最終的な値は、整数valueのビット17です(中括弧に注目してください)。

value{8...15}

最終的な値は、整数valueのビット8〜15です。ビットの順序は、{15...8}を指定することで逆にできます。

value[i]

最終的な値は、リストvalueの要素iです(角括弧に注目してください)。言い換えれば、角括弧はリストの添え字演算子として機能します。これは、単一の要素が指定されている場合にのみ当てはまります。

value[i,]

最終的な値は、リストの単一の要素iを含むリストです。要するに、単一の要素を持つリストスライスです。

value[4...7,17,2...3,4]

最終的な値は、リストvalueのスライスである新しいリストです。新しいリストには、要素4、5、6、7、17、2、3、および4が含まれています。要素は複数回、任意の順序で含めることができます。これは、複数の要素が指定されている場合にのみ結果です。

value[i,m...n,j,ls]

各要素は、式(変数、感嘆符演算子)にすることができます。mnの型はintである必要があります。ij、およびlsの型は、intまたはlist<int>のいずれかである必要があります。

value.field

最終的な値は、指定されたレコードvalueで指定されたfieldの値です。

1.5.3 貼り付け演算子

貼り付け演算子(#)は、TableGen式で使用できる唯一の中置演算子です。文字列またはリストを連結できますが、いくつかの珍しい機能があります。

貼り付け演算子は、DefまたはDefmステートメントでレコード名を指定する場合に使用できます。この場合、文字列を構築する必要があります。オペランドが未定義の名前(TokIdentifier)またはグローバルなDefvarまたはDefsetの名前である場合、文字の逐語的な文字列として扱われます。グローバル名の値は使用されません。

貼り付け演算子は、他のすべての値式で使用でき、その場合、文字列またはリストを構築できます。奇妙ですが、前のケースと一致して、右辺のオペランドが未定義の名前またはグローバル名である場合、文字の逐語的な文字列として扱われます。左辺のオペランドは正常に扱われます。

値は、末尾に貼り付け演算子を持つことができ、その場合、左辺のオペランドは空の文字列に連結されます。

付録B:貼り付け演算子の例に、貼り付け演算子の動作例を示します。

1.6 ステートメント

次のステートメントは、TableGenソースファイルの最上位に表示される場合があります。

TableGenFile ::=  (Statement | IncludeDirective
                 | PreprocessorDirective)*
Statement    ::=  Assert | Class | Def | Defm | Defset | Deftype
                 | Defvar | Dump  | Foreach | If | Let | MultiClass

次のセクションでは、これらの最上位ステートメントのそれぞれについて説明します。

1.6.1 class — 抽象レコードクラスの定義

classステートメントは、他のクラスとレコードが継承できる抽象レコードクラスを定義します。

Class           ::=  "class" ClassID [TemplateArgList] RecordBody
TemplateArgList ::=  "<" TemplateArgDecl ("," TemplateArgDecl)* ">"
TemplateArgDecl ::=  Type TokIdentifier ["=" Value]

クラスは、「テンプレート引数」のリストによってパラメーター化できます。その値は、クラスのレコード本体で使用できます。これらのテンプレート引数は、クラスが別のクラスまたはレコードによって継承されるたびに指定されます。

テンプレート引数が=でデフォルト値が割り当てられていない場合、初期化されておらず(「値」?を持つ)、クラスが継承されるときにテンプレート引数リストで指定する必要があります(必須引数)。引数にデフォルト値が割り当てられている場合、引数リストで指定する必要はありません(オプション引数)。宣言では、すべての必須テンプレート引数は、任意のオプション引数の前に配置する必要があります。テンプレート引数のデフォルト値は、左から右に評価されます。

RecordBodyは以下で定義します。これには、現在のクラスが継承する親クラスのリスト、フィールド定義、およびその他のステートメントを含めることができます。クラスCが別のクラスDから継承する場合、Dのフィールドは、実質的にCのフィールドにマージされます。

特定のクラスは一度しか定義できません。classステートメントは、次のいずれかが真である場合、クラスを定義すると見なされます(RecordBody要素については以下で説明します)。

空のTemplateArgListと空のRecordBodyを指定することで、空のクラスを宣言できます。これは、制限された形式の先行宣言として機能できます。先行宣言されたクラスから派生したレコードは、それらのレコードが宣言の解析時に構築されるため、およびクラスが最終的に定義される前であるため、そこからフィールドを継承しないことに注意してください。

すべてのクラスには、NAME(大文字)という名前の暗黙的なテンプレート引数があります。これは、クラスを継承するDefまたはDefmの名前に関連付けられています。クラスが匿名レコードによって継承される場合、名前は指定されていませんが、グローバルに一意です。

例については、例:クラスとレコードを参照してください。

1.6.1.1 レコード本体

レコード本体は、クラス定義とレコード定義の両方に現れます。レコード本体には、親クラスリストを含めることができ、現在のクラスまたはレコードがフィールドを継承するクラスを指定します。このようなクラスは、そのクラスまたはレコードの親クラスと呼ばれます。レコード本体には、定義の本体部分も含まれており、クラスまたはレコードのフィールドの仕様が含まれます。

RecordBody            ::=  ParentClassList Body
ParentClassList       ::=  [":" ParentClassListNE]
ParentClassListNE     ::=  ClassRef ("," ClassRef)*
ClassRef              ::=  (ClassID | MultiClassID) ["<" [ArgValueList] ">"]
ArgValueList          ::=  PostionalArgValueList [","] NamedArgValueList
PostionalArgValueList ::=  [Value {"," Value}*]
NamedArgValueList     ::=  [NameValue "=" Value {"," NameValue "=" Value}*]

ParentClassListMultiClassID を含む場合は、defm ステートメントのクラスリストでのみ有効です。その場合、ID はマルチクラスの名前である必要があります。

引数の値は、次の2つの形式で指定できます。

  • 位置引数 (value)。値は、対応する位置の引数に割り当てられます。Foo<a0, a1> の場合、a0 は最初の引数に割り当てられ、a1 は2番目の引数に割り当てられます。

  • 名前付き引数 (name=value)。値は、指定された名前の引数に割り当てられます。Foo<a=a0, b=a1> の場合、a0 は名前が a の引数に割り当てられ、a1 は名前が b の引数に割り当てられます。

必須引数も、名前付き引数として指定できます。

引数は、指定方法(名前付きまたは位置)に関係なく、一度しか指定できないことに注意してください。位置引数は名前付き引数の前に置く必要があります。

Body     ::=  ";" | "{" BodyItem* "}"
BodyItem ::=  (Type | "code") TokIdentifier ["=" Value] ";"
             | "let" TokIdentifier ["{" RangeList "}"] "=" Value ";"
             | "defvar" TokIdentifier "=" Value ";"
             | Assert

本体のフィールド定義では、クラスまたはレコードに含めるフィールドを指定します。初期値が指定されていない場合、フィールドの値は初期化されません。型を指定する必要があります。TableGen は値から型を推論しません。キーワード code は、フィールドがコードである文字列値を持つことを強調するために使用できます。

let 形式は、フィールドを新しい値にリセットするために使用されます。これは、本体で直接定義されたフィールド、または親クラスから継承されたフィールドに対して行うことができます。RangeList を指定して、bit<n> フィールドの特定のビットをリセットできます。

defvar 形式は、本体内の他の値式で使用できる変数を定義します。変数はフィールドではありません。定義されているクラスまたはレコードのフィールドにはなりません。変数は、本体の処理中に一時的な値を保持するために提供されます。詳細については、レコード本体での Defvar を参照してください。

クラス C2 がクラス C1 から継承する場合、C1 のすべてのフィールド定義を取得します。これらの定義がクラス C2 にマージされると、C2 によって C1 に渡されたテンプレート引数が定義に代入されます。言い換えれば、C1 によって定義された抽象レコードフィールドは、C2 にマージされる前にテンプレート引数で展開されます。

1.6.2 def — 具体的なレコードを定義する

def ステートメントは、新しい具体的なレコードを定義します。

Def       ::=  "def" [NameValue] RecordBody
NameValue ::=  Value (parsed in a special mode)

名前の値はオプションです。指定した場合、未定義(認識されない)識別子がリテラル文字列として解釈される特別なモードで解析されます。特に、defvar および defset で定義されたグローバル変数を含むグローバル識別子は、認識されないと見なされます。レコード名はヌル文字列にすることができます。

名前の値が指定されていない場合、レコードは *匿名* になります。匿名レコードの最終的な名前は指定されていませんが、グローバルに一意です。

defmulticlass ステートメント内に現れる場合、特別な処理が発生します。詳細については、以下の multiclass セクションを参照してください。

レコードは、レコード本体の先頭に ParentClassList 句を指定することにより、1つ以上のクラスから継承できます。親クラスのすべてのフィールドがレコードに追加されます。2つ以上の親クラスが同じフィールドを提供する場合、レコードは最後の親クラスのフィールド値で終わります。

特別なケースとして、レコードの名前をそのレコードの親クラスへのテンプレート引数として渡すことができます。たとえば

class A <dag d> {
  dag the_dag = d;
}

def rec1 : A<(ops rec1)>;

DAG (ops rec1) は、テンプレート引数としてクラス A に渡されます。DAG には、定義中のレコードである rec1 が含まれていることに注意してください。

新しいレコードを作成するために行われる手順は、やや複雑です。レコードの構築方法を参照してください。

例については、例:クラスとレコードを参照してください。

1.6.3 例:クラスとレコード

以下は、1つのクラスと2つのレコード定義を含む単純な TableGen ファイルです。

class C {
  bit V = true;
}

def X : C;
def Y : C {
  let V = false;
  string Greeting = "Hello!";
}

最初に、抽象クラス C が定義されます。これには、true に初期化されたビットである V という名前の1つのフィールドがあります。

次に、クラス C から派生した、つまり C を親クラスとする2つのレコードが定義されます。したがって、両方とも V フィールドを継承します。レコード Y は、"Hello!" に初期化された別の文字列フィールド Greeting も定義します。さらに、Y は継承された V フィールドをオーバーライドし、false に設定します。

クラスは、複数のレコードの共通機能を1か所に分離するのに役立ちます。クラスは共通フィールドをデフォルト値に初期化できますが、そのクラスから継承するレコードはデフォルトをオーバーライドできます。

TableGen は、パラメータ化されたクラスと非パラメータ化されたクラスの定義をサポートしています。パラメータ化されたクラスは、変数宣言のリストを指定し、オプションで、別のクラスまたはレコードの親クラスとしてクラスが指定されたときにバインドされるデフォルトを持つことができます。

class FPFormat <bits<3> val> {
  bits<3> Value = val;
}

def NotFP      : FPFormat<0>;
def ZeroArgFP  : FPFormat<1>;
def OneArgFP   : FPFormat<2>;
def OneArgFPRW : FPFormat<3>;
def TwoArgFP   : FPFormat<4>;
def CompareFP  : FPFormat<5>;
def CondMovFP  : FPFormat<6>;
def SpecialFP  : FPFormat<7>;

FPFormat クラスの目的は、一種の列挙型として機能することです。3ビットの数値を保持する単一のフィールド Value を提供します。テンプレート引数 val は、Value フィールドを設定するために使用されます。8つのレコードのそれぞれが、親クラスとして FPFormat を使用して定義されています。列挙値は、テンプレート引数として山かっこで囲んで渡されます。各レコードは、適切な列挙値を持つ Value フィールドを継承します。

以下は、テンプレート引数を持つクラスのより複雑な例です。まず、上記の FPFormat クラスに似たクラスを定義します。テンプレート引数を取り、それを使用して Value という名前のフィールドを初期化します。次に、4つの異なる整数値を持つ Value フィールドを継承する4つのレコードを定義します。

class ModRefVal <bits<2> val> {
  bits<2> Value = val;
}

def None   : ModRefVal<0>;
def Mod    : ModRefVal<1>;
def Ref    : ModRefVal<2>;
def ModRef : ModRefVal<3>;

これはやや不自然ですが、Value フィールドの2つのビットを個別に調べたいとします。テンプレート引数として ModRefVal レコードを受け入れ、その値をそれぞれ1ビットの2つのフィールドに分割するクラスを定義できます。次に、ModRefBits から継承し、テンプレート引数として渡される ModRefVal レコードの各ビットに1つずつ、2つのフィールドを取得するレコードを定義できます。

class ModRefBits <ModRefVal mrv> {
  // Break the value up into its bits, which can provide a nice
  // interface to the ModRefVal values.
  bit isMod = mrv.Value{0};
  bit isRef = mrv.Value{1};
}

// Example uses.
def foo   : ModRefBits<Mod>;
def bar   : ModRefBits<Ref>;
def snork : ModRefBits<ModRef>;

これは、あるクラスが別のクラスのフィールドを再編成し、その別のクラスの内部表現を隠すことができる方法を示しています。

例で llvm-tblgen を実行すると、次の定義が出力されます

def bar {      // Value
  bit isMod = 0;
  bit isRef = 1;
}
def foo {      // Value
  bit isMod = 1;
  bit isRef = 0;
}
def snork {      // Value
  bit isMod = 1;
  bit isRef = 1;
}

1.6.4 let — クラスまたはレコードのフィールドをオーバーライドする

let ステートメントは、フィールド値のセット(*バインディング* と呼ばれることもあります)を収集し、let のスコープ内のステートメントによって定義されたすべてのクラスとレコードに適用します。

Let     ::=   "let" LetList "in" "{" Statement* "}"
            | "let" LetList "in" Statement
LetList ::=  LetItem ("," LetItem)*
LetItem ::=  TokIdentifier ["<" RangeList ">"] "=" Value

let ステートメントはスコープを確立します。これは、中かっこで囲まれたステートメントのシーケンス、または中かっこなしの単一のステートメントです。LetList のバインディングは、そのスコープ内のステートメントに適用されます。

LetList内のフィールド名は、ステートメントで定義されたクラスおよびレコードが継承するクラス内のフィールドを指名する必要があります。フィールド値は、レコードが親クラスからすべてのフィールドを継承したに、クラスおよびレコードに適用されます。したがって、letは継承されたフィールド値を上書きする役割を果たします。letは、テンプレート引数の値を上書きすることはできません。

トップレベルのletステートメントは、いくつかのレコードで少数のフィールドを上書きする必要がある場合に役立つことがよくあります。以下に2つの例を示します。letステートメントはネストできることに注意してください。

let isTerminator = true, isReturn = true, isBarrier = true, hasCtrlDep = true in
  def RET : I<0xC3, RawFrm, (outs), (ins), "ret", [(X86retflag 0)]>;

let isCall = true in
  // All calls clobber the non-callee saved registers...
  let Defs = [EAX, ECX, EDX, FP0, FP1, FP2, FP3, FP4, FP5, FP6, ST0,
              MM0, MM1, MM2, MM3, MM4, MM5, MM6, MM7, XMM0, XMM1, XMM2,
              XMM3, XMM4, XMM5, XMM6, XMM7, EFLAGS] in {
    def CALLpcrel32 : Ii32<0xE8, RawFrm, (outs), (ins i32imm:$dst, variable_ops),
                           "call\t${dst:call}", []>;
    def CALL32r     : I<0xFF, MRM2r, (outs), (ins GR32:$dst, variable_ops),
                        "call\t{*}$dst", [(X86call GR32:$dst)]>;
    def CALL32m     : I<0xFF, MRM2m, (outs), (ins i32mem:$dst, variable_ops),
                        "call\t{*}$dst", []>;
  }

トップレベルのletは、クラスまたはレコード自体で定義されたフィールドを上書きしないことに注意してください。

1.6.5 multiclass — 複数のレコードを定義する

テンプレート引数を持つクラスは、複数のレコード間の共通性をファクタリングするのに適した方法ですが、マルチクラスを使用すると、多くのレコードを一度に定義する便利な方法が得られます。たとえば、命令が2つの形式(reg = reg op regおよびreg = reg op imm (例: SPARC))で構成される3アドレス命令アーキテクチャを考えます。これらの2つの共通形式が存在することを1か所で指定し、別の場所ですべての演算を指定したいとします。multiclassステートメントとdefmステートメントを使用すると、この目標を達成できます。マルチクラスは、複数のレコードに展開されるマクロまたはテンプレートとして考えることができます。

MultiClass          ::=  "multiclass" TokIdentifier [TemplateArgList]
                         ParentClassList
                         "{" MultiClassStatement+ "}"
MultiClassID        ::=  TokIdentifier
MultiClassStatement ::=  Assert | Def | Defm | Defvar | Foreach | If | Let

通常のクラスと同様に、マルチクラスには名前があり、テンプレート引数を受け入れることができます。マルチクラスは他のマルチクラスから継承でき、これにより他のマルチクラスが展開され、継承するマルチクラス内のレコード定義に寄与します。マルチクラスの本体には、DefおよびDefmを使用してレコードを定義する一連のステートメントが含まれます。さらに、DefvarForeach、およびLetステートメントを使用して、さらに共通の要素をファクタリングできます。IfステートメントとAssertステートメントも使用できます。

また、通常のクラスと同様に、マルチクラスには暗黙のテンプレート引数NAMEがあります (「NAME」を参照)。名前付き (匿名ではない) レコードがマルチクラスで定義され、レコードの名前がテンプレート引数NAMEの使用を含まない場合、そのような使用は自動的に名前の先頭に付加されます。つまり、マルチクラス内では、以下は同等です

def Foo ...
def NAME # Foo ...

マルチクラスで定義されたレコードは、マルチクラス定義の外部でdefmステートメントによってマルチクラスが「インスタンス化」または「呼び出し」されたときに作成されます。マルチクラス内の各defステートメントは、レコードを生成します。トップレベルのdefステートメントと同様に、これらの定義は複数の親クラスから継承できます。

例については、「例: マルチクラスとdefm」を参照してください。

1.6.6 defm — マルチクラスを呼び出して複数のレコードを定義する

マルチクラスが定義されたら、defmステートメントを使用してそれらを「呼び出し」、これらのマルチクラス内の複数のレコード定義を処理します。これらのレコード定義は、マルチクラス内のdefステートメント、および間接的にdefmステートメントによって指定されます。

Defm ::=  "defm" [NameValue] ParentClassList ";"

オプションのNameValueは、defの名前と同じ方法で形成されます。ParentClassListは、コロンとそれに続く少なくとも1つのマルチクラスと任意の数の通常のクラスのリストです。マルチクラスは通常のクラスより前に記述する必要があります。defmには本体がないことに注意してください。

このステートメントは、指定されたすべてのマルチクラスで定義されたすべてのレコードを、defステートメントによって直接、またはdefmステートメントによって間接的にインスタンス化します。これらのレコードは、親クラスリストに含まれる通常のクラスで定義されたフィールドも受け取ります。これは、defmによって作成されたすべてのレコードに共通のフィールドセットを追加するのに役立ちます。

名前は、defで使用されるのと同じ特別なモードで解析されます。名前が含まれていない場合は、未指定ですがグローバルに一意の名前が提供されます。つまり、次の例は異なる名前になります

defm    : SomeMultiClass<...>;   // A globally unique name.
defm "" : SomeMultiClass<...>;   // An empty name.

defmステートメントは、マルチクラスの本体で使用できます。これが起こる場合、2番目のバリアントは次と同等です

defm NAME : SomeMultiClass<...>;

より一般的には、defmがマルチクラス内で発生し、その名前が暗黙のテンプレート引数NAMEの使用を含まない場合、NAMEが自動的に先頭に付加されます。つまり、マルチクラス内では、以下は同等です

defm Foo        : SomeMultiClass<...>;
defm NAME # Foo : SomeMultiClass<...>;

例については、「例: マルチクラスとdefm」を参照してください。

1.6.7 例: マルチクラスとdefm

次に、multiclassdefmを使用した簡単な例を示します。命令が2つの形式(reg = reg op regおよびreg = reg op imm (即値))で構成される3アドレス命令アーキテクチャを考えてみましょう。SPARCは、そのようなアーキテクチャの例です。

def ops;
def GPR;
def Imm;
class inst <int opc, string asmstr, dag operandlist>;

multiclass ri_inst <int opc, string asmstr> {
  def _rr : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"),
                   (ops GPR:$dst, GPR:$src1, GPR:$src2)>;
  def _ri : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"),
                   (ops GPR:$dst, GPR:$src1, Imm:$src2)>;
}

// Define records for each instruction in the RR and RI formats.
defm ADD : ri_inst<0b111, "add">;
defm SUB : ri_inst<0b101, "sub">;
defm MUL : ri_inst<0b100, "mul">;

ri_instマルチクラスの各使用は、_rrサフィックスを持つレコードと_riを持つレコードの2つのレコードを定義します。マルチクラスを使用するdefmの名前は、そのマルチクラスで定義されたレコードの名前に付加されることを思い出してください。したがって、結果の定義には次の名前が付けられます。

ADD_rr, ADD_ri
SUB_rr, SUB_ri
MUL_rr, MUL_ri

multiclass機能がない場合、命令は次のように定義する必要があります。

def ops;
def GPR;
def Imm;
class inst <int opc, string asmstr, dag operandlist>;

class rrinst <int opc, string asmstr>
  : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"),
           (ops GPR:$dst, GPR:$src1, GPR:$src2)>;

class riinst <int opc, string asmstr>
  : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"),
           (ops GPR:$dst, GPR:$src1, Imm:$src2)>;

// Define records for each instruction in the RR and RI formats.
def ADD_rr : rrinst<0b111, "add">;
def ADD_ri : riinst<0b111, "add">;
def SUB_rr : rrinst<0b101, "sub">;
def SUB_ri : riinst<0b101, "sub">;
def MUL_rr : rrinst<0b100, "mul">;
def MUL_ri : riinst<0b100, "mul">;

defmは、マルチクラス内で他のマルチクラスを「呼び出し」、現在のマルチクラスで定義されたレコードに加えて、それらのマルチクラスで定義されたレコードを作成するために使用できます。次の例では、basic_sマルチクラスとbasic_pマルチクラスには、basic_rマルチクラスを参照するdefmステートメントが含まれています。basic_rマルチクラスには、defステートメントのみが含まれています。

class Instruction <bits<4> opc, string Name> {
  bits<4> opcode = opc;
  string name = Name;
}

multiclass basic_r <bits<4> opc> {
  def rr : Instruction<opc, "rr">;
  def rm : Instruction<opc, "rm">;
}

multiclass basic_s <bits<4> opc> {
  defm SS : basic_r<opc>;
  defm SD : basic_r<opc>;
  def X : Instruction<opc, "x">;
}

multiclass basic_p <bits<4> opc> {
  defm PS : basic_r<opc>;
  defm PD : basic_r<opc>;
  def Y : Instruction<opc, "y">;
}

defm ADD : basic_s<0xf>, basic_p<0xf>;

最後のdefmは、次のレコードを作成します。basic_sマルチクラスからは5つ、basic_pマルチクラスからは5つです。

ADDSSrr, ADDSSrm
ADDSDrr, ADDSDrm
ADDX
ADDPSrr, ADDPSrm
ADDPDrr, ADDPDrm
ADDY

トップレベルとマルチクラス内の両方のdefmステートメントは、マルチクラスに加えて通常のクラスから継承できます。ルールは、通常のクラスはマルチクラスの後にリストする必要があり、少なくとも1つのマルチクラスが存在する必要があるということです。

class XD {
  bits<4> Prefix = 11;
}
class XS {
  bits<4> Prefix = 12;
}
class I <bits<4> op> {
  bits<4> opcode = op;
}

multiclass R {
  def rr : I<4>;
  def rm : I<2>;
}

multiclass Y {
  defm SS : R, XD;    // First multiclass R, then regular class XD.
  defm SD : R, XS;
}

defm Instr : Y;

この例では、フィールドを持つ4つのレコードが、アルファベット順に示されています。

def InstrSDrm {
  bits<4> opcode = { 0, 0, 1, 0 };
  bits<4> Prefix = { 1, 1, 0, 0 };
}

def InstrSDrr {
  bits<4> opcode = { 0, 1, 0, 0 };
  bits<4> Prefix = { 1, 1, 0, 0 };
}

def InstrSSrm {
  bits<4> opcode = { 0, 0, 1, 0 };
  bits<4> Prefix = { 1, 0, 1, 1 };
}

def InstrSSrr {
  bits<4> opcode = { 0, 1, 0, 0 };
  bits<4> Prefix = { 1, 0, 1, 1 };
}

マルチクラス内でletステートメントを使用することもできます。これにより、特に複数のレベルのマルチクラスのインスタンス化を使用する場合に、レコードから共通性をファクタリングする別の方法が提供されます。

multiclass basic_r <bits<4> opc> {
  let Predicates = [HasSSE2] in {
    def rr : Instruction<opc, "rr">;
    def rm : Instruction<opc, "rm">;
  }
  let Predicates = [HasSSE3] in
    def rx : Instruction<opc, "rx">;
}

multiclass basic_ss <bits<4> opc> {
  let IsDouble = false in
    defm SS : basic_r<opc>;

  let IsDouble = true in
    defm SD : basic_r<opc>;
}

defm ADD : basic_ss<0xf>;

1.6.8 defset — 定義セットを作成する

defsetステートメントは、レコードのセットをグローバルなレコードリストに収集するために使用されます。

Defset ::=  "defset" Type TokIdentifier "=" "{" Statement* "}"

defおよびdefmを介して中かっこ内で定義されたすべてのレコードは通常どおりに定義され、指定された名前(TokIdentifier)のグローバルリストにも収集されます。

指定された型はlist<class>である必要があります。ここで、classはレコードクラスです。defsetステートメントは、ステートメントのスコープを確立します。defsetのスコープ内で、class型ではないレコードを定義するとエラーになります。

defset ステートメントはネストできます。内側の defset は自身のセットにレコードを追加し、それらのレコードはすべて外側のセットにも追加されます。

ClassID<...> 構文を使用して初期化式の中で作成された匿名レコードは、セットに収集されません。

1.6.9 deftype — 型の定義

deftype ステートメントは型を定義します。定義された型は、その定義に続くステートメント全体で使用できます。

Deftype ::=  "deftype" TokIdentifier "=" Type ";"

= の左側の識別子は、実際の型が = の右側の型式で与えられる型名として定義されます。

現在、ソース型としてサポートされているのはプリミティブ型と型エイリアスのみであり、deftype ステートメントはトップレベルでのみ記述できます。

1.6.10 defvar — 変数の定義

defvar ステートメントはグローバル変数を定義します。その値は、定義に続くステートメント全体で使用できます。

Defvar ::=  "defvar" TokIdentifier "=" Value ";"

= の左側の識別子は、値が = の右側の値式で与えられるグローバル変数として定義されます。変数の型は自動的に推論されます。

一度定義された変数は、別の値に設定することはできません。

トップレベルの foreach で定義された変数は、各ループの反復処理の終了時にスコープ外になるため、ある反復処理での値は次の反復処理では使用できません。次の defvar は機能しません。

defvar i = !add(i, 1);

変数はレコード本体で defvar を使用して定義することもできます。詳細については、「レコード本体での Defvar」を参照してください。

1.6.11 foreach — ステートメントのシーケンスを反復処理する

foreach ステートメントは、値のシーケンスで変数を変化させながら、一連のステートメントを反復処理します。

Foreach         ::=  "foreach" ForeachIterator "in" "{" Statement* "}"
                    | "foreach" ForeachIterator "in" Statement
ForeachIterator ::=  TokIdentifier "=" ("{" RangeList "}" | RangePiece | Value)

foreach の本体は、中括弧で囲まれた一連のステートメント、または中括弧のない単一のステートメントです。ステートメントは、範囲リスト、範囲の一部、または単一の値の各値について一度再評価されます。各反復処理では、TokIdentifier 変数が値に設定され、ステートメントで使用できます。

ステートメントリストは、内側のスコープを確立します。foreach にローカルな変数は、各ループの反復処理の終了時にスコープ外になるため、それらの値は反復処理間で引き継がれません。Foreach ループはネストできます。

foreach i = [0, 1, 2, 3] in {
  def R#i : Register<...>;
  def F#i : Register<...>;
}

このループは、R0R1R2、および R3 という名前のレコードと、F0F1F2、および F3 を定義します。

1.6.12 dump — stderr にメッセージを出力する

dump ステートメントは、入力文字列を標準エラー出力に出力します。これはデバッグ目的で使用されます。

  • トップレベルでは、メッセージはすぐに印刷されます。

  • レコード/クラス/マルチクラス内では、dump は、包含レコードの各インスタンス化ポイントで評価されます。

Dump ::=  "dump"  string ";"

たとえば、!repr と組み合わせて使用すると、マルチクラスに渡される値を調査できます。

multiclass MC<dag s> {
  dump "s = " # !repr(s);
}

1.6.13 if — テストに基づいてステートメントを選択する

if ステートメントを使用すると、式の値に基づいて 2 つのステートメントグループのいずれかを選択できます。

If     ::=  "if" Value "then" IfBody
           | "if" Value "then" IfBody "else" IfBody
IfBody ::=  "{" Statement* "}" | Statement

値式が評価されます。真 (bang 演算子で使用されるのと同じ意味) に評価される場合、then 予約語に続くステートメントが処理されます。それ以外の場合、else 予約語がある場合は、else に続くステートメントが処理されます。値が偽で、else アームがない場合、ステートメントは処理されません。

then ステートメントを囲む中括弧はオプションであるため、この文法規則には「ダングリング else」句に関する通常あいまいさがあり、通常の方法で解決されます。if v1 then if v2 then {...} else {...} のような場合、else は外側の if ではなく内側の if に関連付けられます。

if の then および else アームの IfBody は、内側のスコープを確立します。本体で定義された defvar 変数は、本体が終了するとスコープ外になります (詳細については、レコード本体での Defvar を参照してください)。

if ステートメントは、レコードの Body でも使用できます。

1.6.14 assert — 条件が真であることを確認する

assert ステートメントは、ブール条件が真であることを確認し、そうでない場合はエラーメッセージを出力します。

Assert ::=  "assert" condition "," message ";"

ブール条件が真の場合、ステートメントは何もしません。条件が偽の場合、致命的ではないエラーメッセージを出力します。任意の文字列式である **メッセージ** は、メモとしてエラーメッセージに含まれます。assert ステートメントの正確な動作は、配置によって異なります。

  • トップレベルでは、アサーションはすぐにチェックされます。

  • レコード定義では、ステートメントは保存され、すべての表明はレコードが完全に構築された後にチェックされます。

  • クラス定義では、アサーションは保存され、クラスから継承するすべてのサブクラスおよびレコードによって継承されます。アサーションは、レコードが完全に構築されたときにチェックされます。

  • マルチクラス定義では、アサーションはマルチクラスの他のコンポーネントと一緒に保存され、defm を使用してマルチクラスがインスタンス化されるたびにチェックされます。

TableGen ファイルでアサーションを使用すると、TableGen バックエンドでのレコードのチェックを簡略化できます。次に、2 つのクラス定義における assert の例を示します。

class PersonName<string name> {
  assert !le(!size(name), 32), "person name is too long: " # name;
  string Name = name;
}

class Person<string name, int age> : PersonName<name> {
  assert !and(!ge(age, 1), !le(age, 120)), "person age is invalid: " # age;
  int Age = age;
}

def Rec20 : Person<"Donald Knuth", 60> {
  ...
}

1.7 追加詳細

1.7.1 有向非巡回グラフ (DAG)

有向非巡回グラフは、TableGen で dag データ型を使用して直接表現できます。DAG ノードは、演算子と 0 個以上の引数 (またはオペランド) で構成されます。各引数は、任意の必要な型にすることができます。別の DAG ノードを引数として使用することで、任意の DAG ノードのグラフを作成できます。

dag インスタンスの構文は次のとおりです。

( 演算子 引数1, 引数2,)

演算子は必須であり、レコードである必要があります。コンマで区切られた 0 個以上の引数を指定できます。演算子と引数には 3 つの形式があります。

形式

意味

引数値

:名前

引数値と関連付けられた名前

名前

設定されていない (初期化されていない) 値を持つ引数名

は任意の TableGen 値にすることができます。名前 (存在する場合) は、ドル記号 ($) で始まる TokVarName である必要があります。名前の目的は、DAG 内の演算子または引数に特定の意味をタグ付けしたり、ある DAG 内の引数を別の DAG 内の同じ名前の引数に関連付けたりすることです。

DAGを操作する際に役立つ以下のbang演算子があります: !con, !dag, !empty, !foreach, !getdagarg, !getdagname, !getdagop, !setdagarg, !setdagname, !setdagop, !size.

1.7.2 レコード本体でのDefvar

グローバル変数を定義するだけでなく、defvarステートメントは、ローカル変数を定義するために、クラスまたはレコード定義のBody内で使用できます。classまたはmulticlassのテンプレート引数を値式で使用できます。変数のスコープは、defvarステートメントから本体の終わりまで拡張されます。スコープ内で異なる値を設定することはできません。defvarステートメントは、スコープを確立するforeachのステートメントリストでも使用できます。

内側のスコープのVという名前の変数は、外側のスコープの変数Vを隠蔽(シャドウイング)します。特に、いくつかのケースがあります。

  • レコード本体のVはグローバルなVを隠蔽します。

  • レコード本体のVはテンプレート引数Vを隠蔽します。

  • テンプレート引数のVはグローバルなVを隠蔽します。

  • foreachステートメントリストのVは、周囲のレコードまたはグローバルスコープのVを隠蔽します。

foreachで定義された変数は、各ループ反復の終わりにスコープ外になるため、ある反復での値は次の反復で使用できません。次のdefvarは機能しません。

defvar i = !add(i, 1)

1.7.3 レコードの構築方法

レコードが構築される際に、TableGenによって次の手順が実行されます。クラスは単に抽象レコードであるため、同じ手順を実行します。

  1. レコード名(NameValue)を構築し、空のレコードを作成します。

  2. ParentClassList内の親クラスを左から右に解析し、各親クラスの祖先クラスを上から下に訪問します。

  1. 親クラスのフィールドをレコードに追加します。

  2. テンプレート引数をそれらのフィールドに代入します。

  3. 親クラスをレコードの継承されたクラスのリストに追加します。

  1. トップレベルのletバインディングをレコードに適用します。トップレベルのバインディングは、継承されたフィールドにのみ適用されることに注意してください。

  2. レコードの本体を解析します。

  • レコードにフィールドを追加します。

  • ローカルのletステートメントに従って、フィールドの値を変更します。

  • defvar変数を定義します。

  1. すべてのフィールドを調べて、フィールド間の参照を解決します。

  2. レコードを最終的なレコードリストに追加します。

フィールド間の参照は、letバインディングが適用された後(ステップ3)に解決されるため(ステップ5)、letステートメントには特別な力があります。例えば

class C <int x> {
  int Y = x;
  int Yplus1 = !add(Y, 1);
  int xplus1 = !add(x, 1);
}

let Y = 10 in {
  def rec1 : C<5> {
  }
}

def rec2 : C<5> {
  let Y = 10;
}

トップレベルのletを使用してYをバインドする場合と、ローカルのletが同じことを行う場合のどちらの場合も、結果は次のようになります。

def rec1 {      // C
  int Y = 10;
  int Yplus1 = 11;
  int xplus1 = 6;
}
def rec2 {      // C
  int Y = 10;
  int Yplus1 = 11;
  int xplus1 = 6;
}

Yplus1は11になります。なぜなら、let Y!add(Y, 1)が解決される前に実行されるからです。この力を賢く使いましょう。

1.8 サブルーチンとしてのクラスの使用

単純な値で説明したように、クラスは式で呼び出してテンプレート引数を渡すことができます。これにより、TableGenは、そのクラスから継承する新しい匿名レコードを作成します。通常どおり、レコードはクラスで定義されたすべてのフィールドを受け取ります。

この機能は、単純なサブルーチン機能として利用できます。クラスは、テンプレート引数を使用してさまざまな変数とフィールドを定義でき、それらは匿名レコードに格納されます。それらのフィールドは、次のようにクラスを呼び出す式で取得できます。フィールドretにサブルーチンの最終値が含まれていると仮定します。

int Result = ... CalcValue<arg>.ret ...;

CalcValueクラスは、テンプレート引数argで呼び出されます。これにより、retフィールドの値が計算され、Resultフィールドの初期化の「呼び出し箇所」で取得されます。この例で作成された匿名レコードは、結果値を保持する以外の目的はありません。

実用的な例を次に示します。クラスisValidSizeは、指定されたバイト数が有効なデータサイズを表しているかどうかを判断します。ビットretが適切に設定されます。フィールドValidSizeは、データサイズでisValidSizeを呼び出し、結果の匿名レコードからretフィールドを取得することで、初期値を取得します。

class isValidSize<int size> {
  bit ret = !cond(!eq(size,  1): 1,
                  !eq(size,  2): 1,
                  !eq(size,  4): 1,
                  !eq(size,  8): 1,
                  !eq(size, 16): 1,
                  true: 0);
}

def Data1 {
  int Size = ...;
  bit ValidSize = isValidSize<Size>.ret;
}

1.9 プリプロセッシング機能

TableGenに組み込まれているプリプロセッサは、単純な条件付きコンパイルのみを目的としています。以下に(やや非公式に)指定されているディレクティブをサポートしています。

LineBegin              ::=  beginning of line
LineEnd                ::=  newline | return | EOF
WhiteSpace             ::=  space | tab
CComment               ::=  "/*" ... "*/"
BCPLComment            ::=  "//" ... LineEnd
WhiteSpaceOrCComment   ::=  WhiteSpace | CComment
WhiteSpaceOrAnyComment ::=  WhiteSpace | CComment | BCPLComment
MacroName              ::=  ualpha (ualpha | "0"..."9")*
PreDefine              ::=  LineBegin (WhiteSpaceOrCComment)*
                            "#define" (WhiteSpace)+ MacroName
                            (WhiteSpaceOrAnyComment)* LineEnd
PreIfdef               ::=  LineBegin (WhiteSpaceOrCComment)*
                            ("#ifdef" | "#ifndef") (WhiteSpace)+ MacroName
                            (WhiteSpaceOrAnyComment)* LineEnd
PreElse                ::=  LineBegin (WhiteSpaceOrCComment)*
                            "#else" (WhiteSpaceOrAnyComment)* LineEnd
PreEndif               ::=  LineBegin (WhiteSpaceOrCComment)*
                            "#endif" (WhiteSpaceOrAnyComment)* LineEnd

MacroNameは、TableGenファイルの任意の場所で定義できます。名前には値がなく、定義されているかどうかを確認するためだけにテストできます。

マクロテスト領域は、#ifdefまたは#ifndefディレクティブで始まります。マクロ名が定義されている場合(#ifdef)または未定義の場合(#ifndef)、ディレクティブと対応する#elseまたは#endifの間のソースコードが処理されます。テストに失敗したが、#else句がある場合は、#else#endifの間のソースコードが処理されます。テストに失敗し、#else句がない場合は、テスト領域のソースコードは処理されません。

テスト領域はネストできますが、適切にネストする必要があります。ファイルで開始された領域は、そのファイルで終了する必要があります。つまり、同じファイルに#endifが必要です。

MacroNameは、*-tblgenコマンドラインで-Dオプションを使用して外部で定義できます。

llvm-tblgen self-reference.td -Dmacro1 -Dmacro3

1.10 付録A:bang演算子

bang演算子は値式で関数として機能します。bang演算子は、1つ以上の引数を受け取り、それらを操作して結果を生成します。演算子がブール値の結果を生成する場合、結果の値はtrueの場合は1、falseの場合は0になります。演算子がブール値の引数をテストする場合、0をfalse、0以外をtrueと解釈します。

警告

!getopおよび!setopbang演算子は、!getdagopおよび!setdagopを推奨するように廃止されています。

!add(a, b, ...)

この演算子は、abなどを加算し、合計を生成します。

!and(a, b, ...)

この演算子は、abなどにビット単位のANDを実行し、結果を生成します。すべての引数が0または1の場合、論理ANDを実行できます。

!cast<type>(a)

この演算子は、a に対してキャストを実行し、その結果を生成します。a が文字列でない場合、例えば intbit の間、またはレコード型の間のような、単純なキャストが実行されます。これにより、レコードをクラスにキャストできます。レコードが string にキャストされると、レコードの名前が生成されます。

a が文字列の場合、それはレコード名として扱われ、定義済みのすべてのレコードのリストから検索されます。結果のレコードは、指定された type であることが期待されます。

例えば、!cast<type>(name) がマルチクラス定義内、またはマルチクラス定義内でインスタンス化されたクラス内に現れ、name がマルチクラスのテンプレート引数を参照しない場合、その名前のレコードはソースファイルの前の方でインスタンス化されている必要があります。name がテンプレート引数を参照する場合、検索は、マルチクラスをインスタンス化する defm ステートメントまで (または、defm が別のマルチクラス内にあり、name が参照する内側のマルチクラスのテンプレート引数が、外側のマルチクラスのテンプレート引数への参照を含む値で置き換えられる場合、さらに後まで) 遅延されます。

a の型が type と一致しない場合、TableGen はエラーを発生させます。

!con(a, b, ...)

この演算子は、DAGノード ab などを連結します。それらのオペレーションは等しい必要があります。

!con((op a1:$name1, a2:$name2), (op b1:$name3))

は、DAGノード (op a1:$name1, a2:$name2, b1:$name3) を生成します。

!cond(cond1 : val1, cond2 : val2, ..., condn : valn)

この演算子は、cond1 をテストし、結果が真であれば val1 を返します。偽の場合、演算子は cond2 をテストし、結果が真であれば val2 を返します。以降も同様です。いずれの条件も真でない場合、エラーが報告されます。

この例は、整数の符号語を生成します。

!cond(!lt(x, 0) : "negative", !eq(x, 0) : "zero", true : "positive")
!dag(op, arguments, names)

この演算子は、指定された演算子と引数を持つDAGノードを作成します。argumentsnames 引数は、同じ長さのリストであるか、初期化されていない (?) 必要があります。names 引数は、list<string> 型である必要があります。

型システムの制限により、arguments は共通の型を持つ項目のリストである必要があります。実際には、これは、それらが同じ型であるか、共通の親クラスを持つレコードである必要があることを意味します。dag 項目と非 dag 項目を混在させることはできません。ただし、? を使用できます。

例: !dag(op, [a1, a2, ?], ["name1", "name2", "name3"]) は、(op a1-value:$name1, a2-value:$name2, ?:$name3) になります。

!div(a, b)

この演算子は、ab で符号付き除算し、商を生成します。0 による除算はエラーを生成します。INT64_MIN を -1 で除算するとエラーが生成されます。

!empty(a)

この演算子は、文字列、リスト、または DAG a が空の場合 1 を生成し、それ以外の場合は 0 を生成します。DAG は引数がない場合に空です。演算子はカウントしません。

!eq( a, b)

この演算子は、ab と等しい場合 1 を生成し、それ以外の場合は 0 を生成します。引数は、bitbitsintstring、またはレコード値である必要があります。!cast<string> を使用して、他の型のオブジェクトを比較します。

!exists<type>(name)

この演算子は、名前が name である、指定された type のレコードが存在する場合 1 を生成し、それ以外の場合は 0 を生成します。namestring 型である必要があります。

!filter(var, list, predicate)

この演算子は、list の要素をフィルタリングすることによって新しい list を作成します。フィルタリングを実行するために、TableGen は変数 var を各要素にバインドし、次に predicate 式を評価します。predicate 式は、おそらく var を参照します。述語はブール値 (bitbits、または int) を生成する必要があります。値は !if と同じように解釈されます。値が 0 の場合、要素は新しいリストに含まれません。値がそれ以外の場合、要素が含まれます。

!find(string1, string2[, start])

この演算子は、string1string2 を検索し、その位置を生成します。検索の開始位置は、start で指定できます。start は 0 から string1 の長さの範囲をとることができます。デフォルトは 0 です。文字列が見つからない場合、結果は -1 です。

!foldl(init, list, acc, var, expr)

この演算子は、list の項目に対して左畳み込みを実行します。変数 acc はアキュムレータとして機能し、init に初期化されます。変数 var は、list 内の各要素にバインドされます。式は各要素に対して評価され、おそらく accvar を使用して累積値を計算します。これは、!foldlacc に格納し直します。acc の型は init と同じです。var の型は list の要素と同じです。exprinit と同じ型である必要があります。

次の例では、RecList 内のレコードリストの Number フィールドの合計を計算します。

int x = !foldl(0, RecList, total, rec, !add(total, rec.Number));

リストをフィルタリングし、一部の要素のみを含む新しいリストを生成することが目標の場合は、!filter を参照してください。

!foreach(var, sequence, expr)

この演算子は、新しい list/dag を作成します。このリスト/DAGでは、各要素が sequence list/dag 内の対応する要素の関数です。関数を実行するために、TableGen は変数 var を要素にバインドし、次に式を評価します。式はおそらく変数 var を参照し、結果値を計算します。

同じ値を複数回繰り返した特定の長さのリストを作成したいだけの場合は、!listsplat を参照してください。

!ge(a, b)

この演算子は、ab 以上の場合 1 を生成し、それ以外の場合は 0 を生成します。引数は、bitbitsint、または string 値である必要があります。

!getdagarg<type>(dag,key)

この演算子は、指定された key (整数インデックスまたは文字列名) によって、指定された dag ノードから引数を取り出します。その引数が指定された type に変換できない場合、? が返されます。

!getdagname(dag,index)

この演算子は、指定された index によって、指定された dag ノードから引数名を取り出します。その引数に関連付けられた名前がない場合、? が返されます。

!getdagop(dag) –または– !getdagop<type>(dag)

この演算子は、指定された dag ノードの演算子を生成します。例: !getdagop((foo 1, 2))foo になります。DAG 演算子は常にレコードであることに注意してください。

!getdagop の結果は、任意のレコードクラスが受け入れられるコンテキスト (通常、別の dag 値に配置する場合) で直接使用できます。ただし、他のコンテキストでは、特定のクラスに明示的にキャストする必要があります。<type> 構文は、これを簡単にするために提供されています。

たとえば、結果を BaseClass 型の値に割り当てるには、次のいずれかを記述できます。

BaseClass b = !getdagop<BaseClass>(someDag);
BaseClass b = !cast<BaseClass>(!getdagop(someDag));

ただし、別の演算子から演算子を再利用する新しいDAGノードを作成する場合、キャストは必要ありません。

dag d = !dag(!getdagop(someDag), args, names);
!gt(a, b)

この演算子は、ab より大きい場合に 1 を生成し、それ以外の場合は 0 を生成します。引数は、bitbitsint、または string 値である必要があります。

!head(a)

この演算子は、リスト a の 0 番目の要素を生成します。(!tail も参照してください。)

!if(test, then, else)

この演算子は、bit または int を生成する必要がある *test* を評価します。結果が 0 でない場合、*then* 式が生成されます。それ以外の場合は、*else* 式が生成されます。

!interleave(list, delim)

この演算子は、*list* 内の項目を連結し、各ペアの間に *delim* 文字列を挿入して、結果の文字列を生成します。リストは、string、int、bits、または bit のリストにすることができます。空のリストの場合、空の文字列になります。区切り文字は空の文字列にすることができます。

!isa<type>(a)

この演算子は、a の型が指定された *type* のサブタイプである場合に 1 を生成し、それ以外の場合は 0 を生成します。

!le(a, b)

この演算子は、ab 以下の場合に 1 を生成し、それ以外の場合は 0 を生成します。引数は、bitbitsint、または string 値である必要があります。

!listconcat(list1, list2, ...)

この演算子は、リスト引数 *list1*、*list2* などを連結して、結果のリストを生成します。リストは同じ要素型を持つ必要があります。

!listremove(list1, list2)

この演算子は、*list2* にも存在するすべての要素を削除した *list1* のコピーを返します。リストは同じ要素型を持つ必要があります。

!listsplat(value, count)

この演算子は、要素がすべて *value* に等しい長さ *count* のリストを生成します。たとえば、!listsplat(42, 3) の結果は [42, 42, 42] になります。

!logtwo(a)

この演算子は、a の底 2 の対数を生成し、整数の結果を生成します。0 または負の数の対数はエラーを生成します。これはフロアリング演算です。

!lt(a, b)

この演算子は、ab より小さい場合に 1 を生成し、それ以外の場合は 0 を生成します。引数は、bitbitsint、または string 値である必要があります。

!mul(a, b, ...)

この演算子は、ab などを乗算して、積を生成します。

!ne(a, b)

この演算子は、ab と等しくない場合に 1 を生成し、それ以外の場合は 0 を生成します。引数は、bitbitsintstring、またはレコード値である必要があります。他の型のオブジェクトを比較するには、!cast<string> を使用します。

!not(a)

この演算子は、整数である必要がある a に対して論理 NOT を実行します。引数 0 は 1 (true) になり、他の引数は 0 (false) になります。

!or(a, b, ...)

この演算子は、ab などに対してビット単位の OR を実行し、結果を生成します。すべての引数が 0 または 1 の場合、論理 OR を実行できます。

!range([start,] end[,step])

この演算子は、半開区間シーケンス [start : end : step)list<int> として生成します。start はデフォルトで 0 であり、*step* は 1 です。step は負の数にすることができ、0 にすることはできません。start < end で *step* が負の場合、または *start* > end で *step* が正の場合、結果は空のリスト []<list<int>> になります。

たとえば

  • !range(4)!range(0, 4, 1) と同等であり、結果は [0, 1, 2, 3] です。

  • !range(1, 4)!range(1, 4, 1) と同等であり、結果は [1, 2, 3] です。

  • !range(0, 4, 2) の結果は [0, 2] です。

  • !range(0, 4, -1) および !range(4, 0, 1) の結果は空です。

!range(list)

!range(0, !size(list)) と同等です。

!repr(value)

value を文字列として表します。値の文字列形式が安定している保証はありません。デバッグ目的のみを対象としています。

!setdagarg(dag,key,arg)

この演算子は、*dag* と同じ演算子と引数を持つ DAG ノードを生成しますが、*key* で指定された引数の値を *arg* に置き換えます。その *key* は、整数のインデックスまたは文字列の名前のいずれかになります。

!setdagname(dag,key,name)

この演算子は、*dag* と同じ演算子と引数を持つ DAG ノードを生成しますが、*key* で指定された引数の名前を *name* に置き換えます。その *key* は、整数のインデックスまたは文字列の名前のいずれかになります。

!setdagop(dag, op)

この演算子は、*dag* と同じ引数を持つ DAG ノードを生成しますが、その演算子は *op* に置き換えられます。

例:!setdagop((foo 1, 2), bar) の結果は (bar 1, 2) になります。

!shl(a, count)

この演算子は、*a* を *count* ビット論理左シフトし、結果の値を生成します。演算は 64 ビット整数に対して実行されます。結果は、0…63 の範囲外のシフト数では未定義です。

!size(a)

この演算子は、文字列、リスト、または DAG *a* のサイズを生成します。DAG のサイズは引数の数です。演算子はカウントしません。

!sra(a, count)

この演算子は、*a* を *count* ビット算術右シフトし、結果の値を生成します。演算は 64 ビット整数に対して実行されます。結果は、0…63 の範囲外のシフト数では未定義です。

!srl(a, count)

この演算子は、acount ビットだけ論理右シフトし、その結果の値を生成します。この演算は 64 ビット整数に対して実行されます。シフト数が 0…63 の範囲外の場合、結果は未定義です。

!strconcat(str1, str2, ...)

この演算子は、文字列引数 str1str2 などを連結し、その結果の文字列を生成します。

!sub(a, b)

この演算子は、a から b を減算し、その算術的な差を生成します。

!subst(target, repl, value)

この演算子は、value 内のすべての target の出現箇所を repl で置き換え、その結果の値を生成します。value が文字列の場合、部分文字列の置換が実行されます。

value がレコード名の場合、この演算子は、target レコード名が value レコード名と等しい場合は repl レコードを生成します。それ以外の場合は value を生成します。

!substr(string, start[, length])

この演算子は、指定された string の部分文字列を抽出します。部分文字列の開始位置は start で指定され、0 から文字列の長さまでの範囲になります。部分文字列の長さは length で指定されます。指定しない場合は、文字列の残りの部分が抽出されます。startlength 引数は整数である必要があります。

!tail(a)

この演算子は、リスト a の 0 番目の要素を除くすべての要素を持つ新しいリストを生成します(!head も参照)。

!tolower(a)

この演算子は、文字列入力 a を小文字に変換します。

!toupper(a)

この演算子は、文字列入力 a を大文字に変換します。

!xor(a, b, ...)

この演算子は、ab などに対してビット単位の排他的論理和を実行し、その結果を生成します。すべての引数が 0 または 1 の場合、論理的な XOR を実行できます。

1.11 付録 B: ペースト演算子の例

レコード名でのペースト演算子の使用例を次に示します。

defvar suffix = "_suffstring";
defvar some_ints = [0, 1, 2, 3];

def name # suffix {
}

foreach i = [1, 2] in {
def rec # i {
}
}

最初の def は、suffix 変数の値を使用しません。2 番目の def は、グローバル名ではないため、i イテレータ変数の値を使用します。以下のレコードが生成されます。

def namesuffix {
}
def rec1 {
}
def rec2 {
}

次に、フィールド値式でのペースト演算子の使用例を示します。

def test {
  string strings = suffix # suffix;
  list<int> integers = some_ints # [4, 5, 6];
}

strings フィールド式は、ペースト演算子の両側で suffix を使用します。左側では通常どおりに評価されますが、右側ではそのまま使用されます。integers フィールド式は、some_ints 変数の値とリテラルリストを使用します。以下のレコードが生成されます。

def test {
  string strings = "_suffstringsuffix";
  list<int> ints = [0, 1, 2, 3, 4, 5, 6];
}

1.12 付録 C: サンプルレコード

LLVM でサポートされているターゲットマシンの 1 つに Intel x86 があります。TableGen からの次の出力は、32 ビットのレジスタ間 ADD 命令を表すために作成されるレコードを示しています。

def ADD32rr { // InstructionEncoding Instruction X86Inst I ITy Sched BinOpRR BinOpRR_RF
  int Size = 0;
  string DecoderNamespace = "";
  list<Predicate> Predicates = [];
  string DecoderMethod = "";
  bit hasCompleteDecoder = 1;
  string Namespace = "X86";
  dag OutOperandList = (outs GR32:$dst);
  dag InOperandList = (ins GR32:$src1, GR32:$src2);
  string AsmString = "add{l}  {$src2, $src1|$src1, $src2}";
  EncodingByHwMode EncodingInfos = ?;
  list<dag> Pattern = [(set GR32:$dst, EFLAGS, (X86add_flag GR32:$src1, GR32:$src2))];
  list<Register> Uses = [];
  list<Register> Defs = [EFLAGS];
  int CodeSize = 3;
  int AddedComplexity = 0;
  bit isPreISelOpcode = 0;
  bit isReturn = 0;
  bit isBranch = 0;
  bit isEHScopeReturn = 0;
  bit isIndirectBranch = 0;
  bit isCompare = 0;
  bit isMoveImm = 0;
  bit isMoveReg = 0;
  bit isBitcast = 0;
  bit isSelect = 0;
  bit isBarrier = 0;
  bit isCall = 0;
  bit isAdd = 0;
  bit isTrap = 0;
  bit canFoldAsLoad = 0;
  bit mayLoad = ?;
  bit mayStore = ?;
  bit mayRaiseFPException = 0;
  bit isConvertibleToThreeAddress = 1;
  bit isCommutable = 1;
  bit isTerminator = 0;
  bit isReMaterializable = 0;
  bit isPredicable = 0;
  bit isUnpredicable = 0;
  bit hasDelaySlot = 0;
  bit usesCustomInserter = 0;
  bit hasPostISelHook = 0;
  bit hasCtrlDep = 0;
  bit isNotDuplicable = 0;
  bit isConvergent = 0;
  bit isAuthenticated = 0;
  bit isAsCheapAsAMove = 0;
  bit hasExtraSrcRegAllocReq = 0;
  bit hasExtraDefRegAllocReq = 0;
  bit isRegSequence = 0;
  bit isPseudo = 0;
  bit isExtractSubreg = 0;
  bit isInsertSubreg = 0;
  bit variadicOpsAreDefs = 0;
  bit hasSideEffects = ?;
  bit isCodeGenOnly = 0;
  bit isAsmParserOnly = 0;
  bit hasNoSchedulingInfo = 0;
  InstrItinClass Itinerary = NoItinerary;
  list<SchedReadWrite> SchedRW = [WriteALU];
  string Constraints = "$src1 = $dst";
  string DisableEncoding = "";
  string PostEncoderMethod = "";
  bits<64> TSFlags = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0 };
  string AsmMatchConverter = "";
  string TwoOperandAliasConstraint = "";
  string AsmVariantName = "";
  bit UseNamedOperandTable = 0;
  bit FastISelShouldIgnore = 0;
  bits<8> Opcode = { 0, 0, 0, 0, 0, 0, 0, 1 };
  Format Form = MRMDestReg;
  bits<7> FormBits = { 0, 1, 0, 1, 0, 0, 0 };
  ImmType ImmT = NoImm;
  bit ForceDisassemble = 0;
  OperandSize OpSize = OpSize32;
  bits<2> OpSizeBits = { 1, 0 };
  AddressSize AdSize = AdSizeX;
  bits<2> AdSizeBits = { 0, 0 };
  Prefix OpPrefix = NoPrfx;
  bits<3> OpPrefixBits = { 0, 0, 0 };
  Map OpMap = OB;
  bits<3> OpMapBits = { 0, 0, 0 };
  bit hasREX_WPrefix = 0;
  FPFormat FPForm = NotFP;
  bit hasLockPrefix = 0;
  Domain ExeDomain = GenericDomain;
  bit hasREPPrefix = 0;
  Encoding OpEnc = EncNormal;
  bits<2> OpEncBits = { 0, 0 };
  bit HasVEX_W = 0;
  bit IgnoresVEX_W = 0;
  bit EVEX_W1_VEX_W0 = 0;
  bit hasVEX_4V = 0;
  bit hasVEX_L = 0;
  bit ignoresVEX_L = 0;
  bit hasEVEX_K = 0;
  bit hasEVEX_Z = 0;
  bit hasEVEX_L2 = 0;
  bit hasEVEX_B = 0;
  bits<3> CD8_Form = { 0, 0, 0 };
  int CD8_EltSize = 0;
  bit hasEVEX_RC = 0;
  bit hasNoTrackPrefix = 0;
  bits<7> VectSize = { 0, 0, 1, 0, 0, 0, 0 };
  bits<7> CD8_Scale = { 0, 0, 0, 0, 0, 0, 0 };
  string FoldGenRegForm = ?;
  string EVEX2VEXOverride = ?;
  bit isMemoryFoldable = 1;
  bit notEVEX2VEXConvertible = 0;
}

レコードの最初の行では、ADD32rr レコードが 8 つのクラスから継承されていることがわかります。継承階層は複雑ですが、親クラスを使用する方が、各命令に対して 109 個の個々のフィールドを指定するよりもはるかに簡単です。

次に、ADD32rr と他の複数の ADD 命令を定義するために使用されるコードフラグメントを示します。

defm ADD : ArithBinOp_RF<0x00, 0x02, 0x04, "add", MRM0r, MRM0m,
                         X86add_flag, add, 1, 1, 1>;

defm ステートメントは、TableGen に ArithBinOp_RF が、BinOpRR_RF から継承する複数の具体的なレコード定義を含むマルチクラスであることを通知します。そのクラスは、さらに BinOpRR から継承し、ITySched から継承します。フィールドはすべての親クラスから継承されます。たとえば、IsIndirectBranchInstruction クラスから継承されます。