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
警告
RangePiece
と SliceElement
の特殊な最後の形式は、「-
」が TokInteger
に含まれているため、1-5
が "1"と "-5"の値を持つ2つの連続したトークンとして字句解析されるためであり、"1", "-", および "5"として解析されるわけではないからです。範囲の句読点としてのハイフンの使用は非推奨です。
1.5.1 単純な値¶
SimpleValue
には、いくつかの形式があります。
SimpleValue ::=TokInteger
|TokString
+ |TokCode
値は、整数リテラル、文字列リテラル、またはコードリテラルにすることができます。複数の隣接する文字列リテラルは、C/C++のように連結されます。単純な値は、文字列の連結です。コードリテラルは文字列になり、それらと区別できなくなります。
SimpleValue2 ::= "true" | "false"
true
と false
リテラルは、本質的に整数値1と0の構文糖衣です。これらは、フィールドの初期化、ビットシーケンス、if
ステートメントなどでブール値が使用されている場合、TableGenファイルの可読性を向上させます。解析されると、これらのリテラルは整数に変換されます。
注意
true
と false
は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
定義における暗黙的なテンプレート引数NAME
(NAMEを参照)。
の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]
各要素は、式(変数、感嘆符演算子)にすることができます。mとnの型はintである必要があります。i、j、およびlsの型は、intまたはlist<int>のいずれかである必要があります。
- value
- 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
内のParentClassList
が存在する場合、またはRecordBody
内のBody
が存在し、空でない場合。
空の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
}*]
ParentClassList
が MultiClassID
を含む場合は、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
で定義されたグローバル変数を含むグローバル識別子は、認識されないと見なされます。レコード名はヌル文字列にすることができます。
名前の値が指定されていない場合、レコードは *匿名* になります。匿名レコードの最終的な名前は指定されていませんが、グローバルに一意です。
def
が multiclass
ステートメント内に現れる場合、特別な処理が発生します。詳細については、以下の 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
を使用してレコードを定義する一連のステートメントが含まれます。さらに、Defvar
、Foreach
、および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¶
次に、multiclass
とdefm
を使用した簡単な例を示します。命令が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<...>;
}
このループは、R0
、R1
、R2
、および R3
という名前のレコードと、F0
、F1
、F2
、および 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によって次の手順が実行されます。クラスは単に抽象レコードであるため、同じ手順を実行します。
レコード名(
NameValue
)を構築し、空のレコードを作成します。ParentClassList
内の親クラスを左から右に解析し、各親クラスの祖先クラスを上から下に訪問します。
親クラスのフィールドをレコードに追加します。
テンプレート引数をそれらのフィールドに代入します。
親クラスをレコードの継承されたクラスのリストに追加します。
トップレベルの
let
バインディングをレコードに適用します。トップレベルのバインディングは、継承されたフィールドにのみ適用されることに注意してください。レコードの本体を解析します。
レコードにフィールドを追加します。
ローカルの
let
ステートメントに従って、フィールドの値を変更します。
defvar
変数を定義します。
すべてのフィールドを調べて、フィールド間の参照を解決します。
レコードを最終的なレコードリストに追加します。
フィールド間の参照は、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
および!setop
bang演算子は、!getdagop
および!setdagop
を推奨するように廃止されています。
!add(
a,
b, ...)
この演算子は、a、bなどを加算し、合計を生成します。
!and(
a,
b, ...)
この演算子は、a、bなどにビット単位のANDを実行し、結果を生成します。すべての引数が0または1の場合、論理ANDを実行できます。
!cast<
type>(
a)
この演算子は、a に対してキャストを実行し、その結果を生成します。a が文字列でない場合、例えば
int
とbit
の間、またはレコード型の間のような、単純なキャストが実行されます。これにより、レコードをクラスにキャストできます。レコードがstring
にキャストされると、レコードの名前が生成されます。a が文字列の場合、それはレコード名として扱われ、定義済みのすべてのレコードのリストから検索されます。結果のレコードは、指定された type であることが期待されます。
例えば、
!cast<
type>(
name)
がマルチクラス定義内、またはマルチクラス定義内でインスタンス化されたクラス内に現れ、name がマルチクラスのテンプレート引数を参照しない場合、その名前のレコードはソースファイルの前の方でインスタンス化されている必要があります。name がテンプレート引数を参照する場合、検索は、マルチクラスをインスタンス化するdefm
ステートメントまで (または、defm が別のマルチクラス内にあり、name が参照する内側のマルチクラスのテンプレート引数が、外側のマルチクラスのテンプレート引数への参照を含む値で置き換えられる場合、さらに後まで) 遅延されます。a の型が type と一致しない場合、TableGen はエラーを発生させます。
!con(
a,
b, ...)
この演算子は、DAGノード a、b などを連結します。それらのオペレーションは等しい必要があります。
!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ノードを作成します。arguments と names 引数は、同じ長さのリストであるか、初期化されていない (
?
) 必要があります。names 引数は、list<string>
型である必要があります。型システムの制限により、arguments は共通の型を持つ項目のリストである必要があります。実際には、これは、それらが同じ型であるか、共通の親クラスを持つレコードである必要があることを意味します。
dag
項目と非dag
項目を混在させることはできません。ただし、?
を使用できます。例:
!dag(op, [a1, a2, ?], ["name1", "name2", "name3"])
は、(op a1-value:$name1, a2-value:$name2, ?:$name3)
になります。!div(
a,
b)
この演算子は、a を b で符号付き除算し、商を生成します。0 による除算はエラーを生成します。INT64_MIN を -1 で除算するとエラーが生成されます。
!empty(
a)
この演算子は、文字列、リスト、または DAG a が空の場合 1 を生成し、それ以外の場合は 0 を生成します。DAG は引数がない場合に空です。演算子はカウントしません。
!eq(
a, b)
この演算子は、a が b と等しい場合 1 を生成し、それ以外の場合は 0 を生成します。引数は、
bit
、bits
、int
、string
、またはレコード値である必要があります。!cast<string>
を使用して、他の型のオブジェクトを比較します。!exists<
type>(
name)
この演算子は、名前が name である、指定された type のレコードが存在する場合 1 を生成し、それ以外の場合は 0 を生成します。name は string 型である必要があります。
!filter(
var,
list,
predicate)
この演算子は、list の要素をフィルタリングすることによって新しい
list
を作成します。フィルタリングを実行するために、TableGen は変数 var を各要素にバインドし、次に predicate 式を評価します。predicate 式は、おそらく var を参照します。述語はブール値 (bit
、bits
、またはint
) を生成する必要があります。値は!if
と同じように解釈されます。値が 0 の場合、要素は新しいリストに含まれません。値がそれ以外の場合、要素が含まれます。
!find(
string1,
string2[,
start])
この演算子は、string1 で string2 を検索し、その位置を生成します。検索の開始位置は、start で指定できます。start は 0 から string1 の長さの範囲をとることができます。デフォルトは 0 です。文字列が見つからない場合、結果は -1 です。
!foldl(
init,
list,
acc,
var,
expr)
この演算子は、list の項目に対して左畳み込みを実行します。変数 acc はアキュムレータとして機能し、init に初期化されます。変数 var は、list 内の各要素にバインドされます。式は各要素に対して評価され、おそらく acc と var を使用して累積値を計算します。これは、
!foldl
が acc に格納し直します。acc の型は init と同じです。var の型は list の要素と同じです。expr は init と同じ型である必要があります。次の例では、
RecList
内のレコードリストのNumber
フィールドの合計を計算します。int x = !foldl(0, RecList, total, rec, !add(total, rec.Number));
リストをフィルタリングし、一部の要素のみを含む新しいリストを生成することが目標の場合は、
!filter
を参照してください。!foreach(
var,
sequence,
expr)
この演算子は、新しい
list
/dag
を作成します。このリスト/DAGでは、各要素が sequencelist
/dag
内の対応する要素の関数です。関数を実行するために、TableGen は変数 var を要素にバインドし、次に式を評価します。式はおそらく変数 var を参照し、結果値を計算します。同じ値を複数回繰り返した特定の長さのリストを作成したいだけの場合は、
!listsplat
を参照してください。!ge(
a, b)
この演算子は、a が b 以上の場合 1 を生成し、それ以外の場合は 0 を生成します。引数は、
bit
、bits
、int
、または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)
この演算子は、a が b より大きい場合に 1 を生成し、それ以外の場合は 0 を生成します。引数は、
bit
、bits
、int
、または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)
この演算子は、a が b 以下の場合に 1 を生成し、それ以外の場合は 0 を生成します。引数は、
bit
、bits
、int
、または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)
この演算子は、a が b より小さい場合に 1 を生成し、それ以外の場合は 0 を生成します。引数は、
bit
、bits
、int
、またはstring
値である必要があります。!mul(
a,
b, ...)
この演算子は、a、b などを乗算して、積を生成します。
!ne(
a, b)
この演算子は、a が b と等しくない場合に 1 を生成し、それ以外の場合は 0 を生成します。引数は、
bit
、bits
、int
、string
、またはレコード値である必要があります。他の型のオブジェクトを比較するには、!cast<string>
を使用します。!not(
a)
この演算子は、整数である必要がある a に対して論理 NOT を実行します。引数 0 は 1 (true) になり、他の引数は 0 (false) になります。
!or(
a,
b, ...)
この演算子は、a、b などに対してビット単位の 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)
この演算子は、a を count ビットだけ論理右シフトし、その結果の値を生成します。この演算は 64 ビット整数に対して実行されます。シフト数が 0…63 の範囲外の場合、結果は未定義です。
!strconcat(
str1,
str2, ...)
この演算子は、文字列引数 str1、str2 などを連結し、その結果の文字列を生成します。
!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 で指定されます。指定しない場合は、文字列の残りの部分が抽出されます。start と length 引数は整数である必要があります。
!tail(
a)
この演算子は、リスト a の 0 番目の要素を除くすべての要素を持つ新しいリストを生成します(
!head
も参照)。!tolower(
a)
この演算子は、文字列入力 a を小文字に変換します。
!toupper(
a)
この演算子は、文字列入力 a を大文字に変換します。
!xor(
a,
b, ...)
この演算子は、a、b などに対してビット単位の排他的論理和を実行し、その結果を生成します。すべての引数が 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
から継承し、ITy
と Sched
から継承します。フィールドはすべての親クラスから継承されます。たとえば、IsIndirectBranch
は Instruction
クラスから継承されます。