日本語の資料が無くて辛かったので,公式ドキュメントの意訳・要約をしていきます.

agenda

とは

TableGenはLLVM project内で使われるDSLで,主にターゲットマシンに依存したコード等の生成に用いられている.

TableGen Overview TableGen Programmer’s Reference

Lexical_Analysis

includeもある.

全体的に構文はPythonのものを模倣している.

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

Literals

数値リテラル

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

DecimalIntegerでは+-が単項演算子としては扱われない.

文字列リテラル

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

エスケープは\で行う

\\ \' \" \t \n

Identifiers

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

case-sensitive

予約語

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

Bang operators

BangOperator ::=  one of
                  !add        !and         !cast        !con         !dag
                  !empty      !eq          !filter      !find        !foldl
                  !foreach    !ge          !getdagop    !gt          !head
                  !if         !interleave  !isa         !le          !listconcat
                  !listsplat  !lt          !mul         !ne          !not
                  !or         !setdagop    !shl         !size        !sra
                  !srl        !strconcat   !sub         !subst       !substr
                  !tail       !xor

Bang演算子とは別に,condもある

CondOperator :: =!cond

詳細はAppendix: Bang Operators

Include files

IncludeDirective ::=  "include" TokString

Cのincludeと同じ

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

等のプリプロセッサディレクティブもある

Type

型システムもある.

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

bit

bitは0 or 1のbool値

int

intは64bitの整数

string

stringは任意長の文字列

bits

bitsは任意の長さn bitの固定サイズの整数で,各bitに個別にアクセスが可能.

list

list<type>で指定された型の要素を持つリスト.zero-indexed

dag

nestable directed acyclic graph(有向非巡回グラフ)

各ノードはOperatorと0個以上の引数(またはオペランド)を持つ

詳細はDirected_Acyclic_Graphs

ClassID

型コンテキストでクラス名を指定する際には,定義された値の型が指定されたクラスのサブクラスでなければいけない.

例えば,list<Register>にはRegisterクラスから派生した定義のみを含める事ができる.

Values_and_Expressions

TableGenのステートメントには,値が必要なコンテキストが多くある.

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

RangePieceが特殊なのは-TokIntegerに含まれている為で,1-5"1", "-", "5"ではなく,"1", "-5"として扱われるから. 範囲指定には-ではなく...を用いる事が推奨されている.

Simple_Values

SimpleValueには様々な形がある

SimpleValue ::=  TokInteger | TokString+ | TokCode

Valueは,整数リテラル,文字列リテラル,またはコードリテラルで,隣接する複数の文字列リテラルはCやC++のように連結される. また,コードリテラルはparse後には文字列となる.


SimpleValue2 ::=  "true" | "false"

truefalseはCのように基本的に1と0のalias. parse後には整数となる.


SimpleValue3 ::=  "?"

?は初期化されていない値を示す.


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

bits<n>の初期化に用いられる.合計でn個のbitを示す必要がある.


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

listの初期化に用いられる.Typeで型を示す事ができるが,そうでない場合は推論が行われる.


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

DAGの初期化に用いられる.最初のDagArgはDAGの演算子と呼ばれ,レコードでなければいけない.


SimpleValue7 ::=  TokIdentifier

parse後は,識別子で指定されたエンティティの値となる.

以下は例

class Foo <int Bar> {
      int Baz = Bar;
}
class Foo {
      int Bar = 5;
      int Baz = Bar;
}
def Bar : SomeClass {
      int X = 5;
}

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

レコードの親クラスから継承されたフィールドにも同様にアクセスできる.

multiclass Foo <int Bar> {
      def : SomeClass<Bar>;
}
foreach i = 0...5 in
  def Foo#i;

SimpleValue8 ::=  ClassID "<" ValueListNE ">"

新しく無名のレコード定義を作成し,そのパース後の値は作成されたレコードとなる. このようにしてクラスを呼び出す事で,サブルーチンの様な機能を提供できる.


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

BangOperatorは他のSimpleValueには無い機能を持つ.

!condを除き,BangOperator()で囲まれた引数となるリストを受け取り,それらの引数に対して何らかの関数を実行し,値を生成する. !cond:で区切られた引数のペアのリストを受け取る.

関数マクロみたいな感じ

Suffixed_values

上記のSimple Valueの値は,特定のsuffixで指定する事ができる.

あるValueのsubvalueを求める為にsuffixを用いる.以下は例

value{17}

value(整数値)の17bit目

value{8...15}

valueの8~15bit.{15...8}の様に指定する事で逆順にもできる.

value[4]

listの4つめの要素(zero-indexed)

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

listの値をスライスした新しいlist. この場合,listには,要素4,5,6,7,17,2,3,4が含まれる.

要素は複数回,任意の順序で含める事ができる.

value.field

指定したレコード値の指定したフィールドの値.

The_paste_operator

paste演算子#はTableGen唯一の中置演算子. この演算子では,文字列やリストを連結できる.

DefまたはDefm文でレコードを生成するときにも使用できるが,その場合は文字列を構築する必要がある.

オペランドが未定義の名前(TokIdentifier)やグローバルなDefvar, Defsetの場合,そのまま文字列として扱われる.グローバルな名前は展開されない.

paste演算子は他のすべての値の式で使用し,文字列またはリストを構築する事ができる.

右辺値が未定義の名前またはグローバルな名前の場合は前述の通りそのままの文字列として扱われるが,左辺値は通常通り扱われる.

Statement

以下の文はTableGenのソースファイルのトップレベルに現れることがある.

TableGenFile ::=  Statement*
Statement    ::=  Assert | Class | Def | Defm | Defset | Defvar
                 | Foreach | If | Let | MultiClass

class

define an abstract record class

class文は他のクラスやレコードが参照できる抽象的なレコードクラスを定義する.

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

クラスはテンプレート引数のリストでパラメータ化する事ができ,その値はクラスのレコード本体で使用できる.

テンプレート引数に=でデフォルトの値が指定されていない場合,その引数は初期化されない. (?が指定されている場合はオプションで,指定する必要は無い)

また,宣言において,総ての必須テンプレート引数はオプション引数より先に無ければいけない.

また,テンプレート引数のデフォルト値は,左から右に評価される.

RecordBodyは,現在のクラスが継承する親クラスのリストや,フィールド定義等を含める事が出来る.

クラスCが別のクラスDを継承した場合,Dのフィールドは事実上Cのフィールドに統合される.

クラス文は,以下のいずれかが成り立つ時,クラスを定義したとみなされる.

空のTemplateArgListRecordBodyを指定する事で,空のクラスを定義する事ができる. これは,前方宣言の制限された値として使用できる. 前方宣言されたクラスから派生したレコードは,そのクラスからフィールドを継承しない. これらのレコードは,宣言がparseされる時に構築されるため,クラスが実際に定義される前に構築されるからである.

すべてのクラスには,NAMEという暗黙のテンプレート引数があり,これはそのクラスを継承するDefDefmの名前に束縛される.

クラスが無名のレコードに継承されている場合,名前は特定されないが,グローバルに一意である.

Record Bodies

Record Bodyはクラス定義とレコード定義の両方に現れる.

Record Bodyには,親クラスのリストを含める事ができ,現在のクラスやレコードがフィールドを継承するクラスを指定できる.

レコード本体には,クラスやレコードのフィールドの仕様を記載した定義の本体も含まれる.

RecordBody        ::=  ParentClassList Body
ParentClassList   ::=  [":" ParentClassListNE]
ParentClassListNE ::=  ClassRef ("," ClassRef)*
ClassRef          ::=  (ClassID | MultiClassID) ["<" [ValueList] ">"]

MultiClassIDを含むParentClassListは,defm文のクラスのリストでのみ有効で,IDはマルチクラスの名前でなければならない.

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

ボディ内のフィールド定義では,クラスやレコードに含まれるフィールドを指定する.

初期値が指定されていない場合,そのフィールドの値は初期化されない.

型は必ず指定する必要があり,ここでは型推論は行われない.

"code"は,フィールドがコードである文字列値を持つことを強調する為に用いる事が出来る.

letは,フィールドを新しい値にセットするのに使用する. これはボディで直接定義されたフィールドや,親クラスから継承されたフィールドに対して行うことが出来る. bits<n>なフィールドの特定のビットをリセットするためにRangeListを指定する事もできる.

defvarでは,変数を定義し,その値をボディ内の他の値の式で使用する事ができる. この変数はフィールドでなく,定義されたクラスやレコードのフィールドにはならない. 変数は,ボディの処理中に一時的な値を保持する為に用意されている.

C2クラスがC1クラスを継承すると,C1クラスのすべてのフィールド定義を取得する. それらの定義がC2クラスにマージされると,C2からC1に渡されたテンプレート引数はすべて定数に置き換えられる. つまり,C1で定義されたレコードの抽象フィールドは,C2にマージされる前にテンプレート引数で拡張される.

def

def文は,新しく具体レコードを定義する.

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

NameValueはオプションで,指定された場合,未定義の識別子を文字列リテラルとしてparseする特殊なモードでparseが行われる. また,defvardefsetで定義されるグローバルな識別子はparseされない.レコード名にはnull文字を指定できる.

NameValueが与えられていない場合,レコードは匿名となる.匿名レコードの名前は特定されないが,グローバルに一意である.

defmulticlass文の中に現れた場合,特殊な処理が行われる.(multiclass)

レコードは,レコード本体の先頭にParentClassListを指定する事で,1つ以上のクラスを継承できる. 親クラスのすべてのフィールドがレコードに追加され,複数の親クラスが同じフィールドを提供する場合,レコードは最後のクラスのフィールドの値で更新される. 特殊なケースとして,レコードの名前をテンプレート引数として,そのレコードの親クラスに渡すことができる. 例えば,

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

def rec1 : A<(ops rec1)>

DAG(ops rec1)はクラスAのテンプレート引数として渡される.DAGには,定義されるレコードであるrecが含まれている. 新しいレコードを定義する手順はHow records are builtを参照

例は次節

Examples: classes and records

ここでは,1つのクラスと2つのレコード定義を持つ単純なTableGenファイルを示す.

class C {
  bit V = true;
}

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

まず,抽象クラスCを定義する.このクラスにはVというbitのフィールドがあり,trueで初期化されている.

次に,クラスCから派生した2つのレコードが定義される.これらはクラスCを親クラスとしている為,両方ともVというフィールドを持つ. レコードYは,さらにGreetingというフィールドを定義しており,これは"Hello!"で初期化されている. また,レコードYはVfalse`で上書きしている.

クラスは,複数のレコードに共通する機能を一箇所に集めるのに便利. 共通のフィールドをデフォルト値で初期化できるが,そのクラスを継承したレコードはデフォルト値を上書きできる.

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クラスは,ある種の列挙型として機能する. このクラスは,3bitの数値を保持するValueという1つのフィールドを提供する. テンプレート引数のvalValueへ値を設定するのに用いられる.

8つのレコードはそれぞれFPFormatを親クラスとするよう定義されている. テンプレート引数は,<>で渡され,各レコードは与えられた値でValueフィールドを継承する.


次に,テンプレート引数を持つクラスのより複雑な例を示す.

まず,上記のFPFormatと同様のクラスを定義する. このクラスはテンプレート引数を受け取り,それを使ってValueというフィールドを初期化sるう. 次に,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レコードの各ビットに対応する2つのフィールドをModRefBitsから取得すれば良い.

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 {       // ModRefBits
  bit isMod = 0;
  bit isRev = 1;
}
def foo {       // ModRefBits
  bit isMod = 1;
  bit isRev = 0;
}
def snork {     // ModRefBits
  bit isMod = 1;
  bit isRev = 1;
}

let

let文は,文中で定義されているすべてのクラスとレコードのフィールド値に束縛を適用する.

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

let文はスコープを設定する. スコープは,{}で囲まれた一連の文,もしくは{}のない単一の文で構成される. LetListの束縛は,そのスコープ内の文に適用される.

LetListのフィールド名は,文で定義されたクラスやレコードが継承するクラスのフィールド名でなければならない. フィールド値は,レコードがその親クラスからすべてのフィールドを継承した後に,クラスとレコードに適用される. そのため,letは継承されたフィールド値をオーバーライドする役割を果たす. しかし,letは,テンプレート引数の値を上書きすることはできない.

トップレベルのlet文は,いくつかのレコードでいくつかのフィールドをオーバーライドする必要がある場合に役立つ. また,let文はねストできる.

以下に2つの例を示す.

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は,クラスやレコード自体に定義されたフィールドを上書きしない.

multiclass

テンプレート引数を持つクラスは,複数のレコード間の共通性を考慮するのに適しているが,multiclassは一度に多くのレコードを定義するのに便利.

例えば,3アドレスの命令アーキテクチャで命令が2つの形式であるとする.

このような2つの共通したフォーマットが存在する事を一箇所で指定し,別の場所ですべての演算を指定したいとする. この目的を達成するのがmulticlassdefm文である.

マルチクラスは,複数のレコードに展開されるマクロやテンプレートと考える事が出来る.

MultiClass           ::=  "multiclass" TokIdentifier [TemplateArgList]
                          [":" ParentMultiClassList]
                          "{" MultiClassStatement+ "}"
ParentMultiClassList ::=  MultiClassID ("," MultiClassID)*
MultiClassID         ::=  TokIdentifier
MultiClassStatement  ::=  Assert | Def | Defm | Defvar | Foreach | If | Let

通常のクラスと同様に,マルチクラスは名前を持ち,テンプレート引数を受け取る事ができる. マルチクラスは他のマルチクラスを継承する事ができ,その場合,他のマルチクラスは拡張され,継承したマルチクラスのレコード定義に寄与する. マルチクラスのボディには,DefDefmを使ってレコードを定義する一連の文が含まれる. さらに,DefvarForeachLet文を使って,共通の要素をくくり出す事ができる. また,If文やAssert文も使える.

また,通常のクラスと同様に,マルチクラスは暗黙のテンプレート引数NAMEを持ち,名前付きの(匿名でない)レコードがマルチクラスで定義され,レコードの名前にテンプレート引数NAMEを使用した名前が含まれていない場合,自動的に名前が付加される.

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

マルチクラスで定義されたレコードは,マルチクラスの定義の外にあるdefm文によって,マルチクラスがインスタンス化または呼び出された時に作成される. マルチクラス内のdef文はそれぞれレコードを生成する. トップレベルのdef文と同様に,これらの定義はふくすの親クラスから継承することができる.

参照: Examples: multiclass and defm

defm

マルチクラスが定義されると,defm文を使ってマルチクラスを呼び出し,そのマルチクラスの複数のレコード定義を処理する. これらのレコード定義は,マルチクラスのdef文で指定され,defm文で間接的に指定される.

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

オプションのNameValueは,defの名前と同様に形成される. ParentClassListはコロンの後に少なくとも1つのマルチクラスと任意の数の通常のクラスのリストを続ける. マルチクラスは,通常のクラスよりも前に無ければいけない. なお,defmはボディを持たない.

この文は,def文によって直接,またはdefm文によって間接的に,指定されたすべてのマルチクラスで定義されたすべてのレコードをインスタンス化する. これらのレコードは,親クラスのリストに含まれる通常のクラスで定義されたフィールドも受け取る. これは,defmによって作成されたすべてのレコードに共通のフィールドを追加するのに便利.

名前はdefで使用されるのと同じ,特殊なモードでparseされる. 名前が含まれていない場合は,グローバルにユニークな名前が割り当てられる.

つまり,以下の例では異なる名前が付けられる

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<...>;

参照: Examples: multiclass and defm

Examples_multiclass_and_defm

ここでは,multiclassとdefmを使った簡単な例を紹介する. 3アドレスの命令アーキテクチャで,命令にはreg = reg op regreg = reg op imm(immediate)の2つの形式があると考える. このようなアーキテクチャの例としては,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マルチクラスを使用すると,prefixが_rrのものと_riのものの2つのレコードが定義される. マルチクラスを使用するdefmの名前は,そのマルチクラスで定義されたレコードの名前の前に付けられるので,結果として得られる定義の名前は

ADD_rr, ADD_ri
SUB_rr, SUB_ri
MUL_rr, MUL_ri

マルチクラス機能がなければ,命令は次のように定義する必要がある.

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_sbasic_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>;

defset

defset文は,レコードのセットをグローバルなレコードのリストに集めるために使用される.

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

中括弧の中でdefdefmを介して定義されたレコードはすべて通常通り定義され,さらにそれらは与えられた名前(TokIdentifier)のグローバルリストに集められる.

指定された型はlist<class>でなければならず,classは何らかのレコード・クラスである. defset文は,その文のスコープを確立する. defsetのスコープ内でclass型ではないレコードを定義するとエラーになる.

defset文はネストすることができる. 内側のdefsetはレコードを自分のセットに追加し,それらのレコードはすべて外側のセットにも追加される.

ClassID<...>構文を使用した初期化式の内部で作成された匿名レコードは,セットに収集されない.

defvar

defvar文は,グローバル変数を定義する. その値は,定義の後に続く文の中で使用することができる.

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

左側の識別子はグローバル変数として定義され,その値は右側の値式で与えられる.

一度定義された変数は,他の値に設定することはできない.

トップレベルのforeachで定義された変数は,各ループの繰り返しの最後にスコープから外れ,ある繰り返しで定義された値は次の繰り返しでは使用できない. 次のようなdefvarは動作しない.

defvar i = !add(i, 1)

変数はレコード本文の中でdefvarを使って定義することもできる. 詳しくはDefvar in a Record Bodyを参照.

foreach

foreach文は,一連の文を繰り返し実行し,変数を一連の値に変化させる.

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

foreachの本体は,中括弧で囲まれた一連の文,または中括弧のない単一の文です. 文は,RangeListRangePiece,または単一の値の各値に対して1回再評価される. 反復のたびに,TokIdentifier変数に値が設定され,文中で使用できる.

文のリストは内側のスコープを確立する. foreachにローカルな変数は,ループの各反復の終わりにスコープから外れるので,その値は反復から次の反復へと引き継がれない. foreachループはネストする事が出来る.

foreach文は,レコードボディでも使用できる.

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

このループでは,R0R1R2R3というレコードと,F0F1F2F3が定義されている.

if

if文では,式の値に基づいて2つのステートメントグループのうち1つを選択することができる.

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

値式が評価される. 評価結果が(bang演算子と同じ意味で)trueであれば,then予約語に続く文が処理されます. そうでない場合,else予約語があれば,elseに続く文が処理されます. 値が偽で,elseの腕がない場合は,どの文も処理されません.

if v1 then if v2 then {...} else {...}のようなケースでは,elseは外側のifではなく内側のifに関連付けられる.

ifのthenとelseのifBodyは,内側のスコープを確立する. ボディ内で定義されたdefvar変数は,ボディが終了するとスコープから外れる(詳細はDefvar_in_a_Record_Bodyを参照).

if文はレコードボディの中でも使うことができる.

assert

assert文は,条件が真であるかどうかをチェックし,真でない場合はエラーメッセージを表示します.

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

条件が真の場合,文は何もしない. 条件が偽の場合は,致命的ではないエラーメッセージを表示する. このメッセージは,任意の文字列表現で,エラーメッセージの中にメモとして含まれる. assert文の正確な動作は,その配置によって異なる.

TableGenファイルでアサーションを使用すると,TableGenバックエンドでのレコードのチェックが簡単になる. ここでは,2 つのクラス定義でアサートを使用した例を示す.

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> {
  ...
}

Additional_Details

Directed_Acyclic_Graphs

directed acyclic graph は,TableGen ではdagデータ型を使って直接表現できる. DAGノードは,1つの演算子と0個以上の引数(またはオペランド)から構成される. 各引数は,任意の型にすることができる. 別のDAGノードを引数として使用することで,DAGノードの任意のグラフを構築することができる.

dagインスタンスの構文は以下の通り.

( operator argument1, argument2, ... )

operatorは必ず存在し,レコードである必要がある. 引数は0個以上で,カンマで区切ることができる. 演算子と引数には3つの形式がある.

Format Meaning
value argument value
value:name argument value and associated name
name argument name with unset(uninitialized) value

valueは,TableGenの任意の値を指定できます. 名前がある場合は,ドル記号($)で始まるTokVarNameでなければならない. 名前の目的は,DAG内の演算子や引数に特定の意味を持たせたり,あるDAG内の引数を別のDAG内の同じ名前の引数と関連付けたりすること.

DAGを扱う際には,以下のbang演算子が便利

!con, !dag, !empty, !foreach, !getdagop, !setdagop, !size.

Defvar_in_a_Record_Body

defvar文は,グローバル変数の定義に加えて,クラスやレコード定義のBody内でローカル変数を定義することができる. 変数のスコープはdefvar文からボディの最後までとなる. スコープ内で異なる値に設定することはできない. defvar文はforeachのステートメントリストでも使用でき,スコープを確立する.

内側のスコープにあるVという変数は,外側のスコープにある変数Vをシャドウイングする(隠す). 特に,レコードボディ内のVはグローバルなVのshadowとなり,foreach文のリスト内のVは周囲のレコードやグローバルスコープ内のVのshadowとなる.

foreachの中で定義された変数は,各ループの繰り返しの終わりにスコープから外れるので,ある繰り返しでの値は次の繰り返しでは利用できない. 以下のようなdefvarは動作しない.

defvar i = !add(i, 1)

How_records_are_built

レコードが作成されると,TableGenは次のステップへ移る. クラスは単に抽象的なレコードなので,同じステップをたどることになる.

  1. レコード名(NameValue)を構築し,空のレコードを作成する
  2. ParentClassListの親クラスを左から右に解決し,各親クラスの祖先クラスを上から下にたどる.
    • a. 親クラスのフィールドをレコードに追加する
    • b. これらのフィールドにテンプレート引数を代入する
    • c. レコードの継承クラスのリストに親クラスを追加する
  3. トップレベルの let バインディングをレコードに適用する. トップレベルのバインディングは,継承されたフィールドにのみ適用される.
  4. レコード本文をパースする.
    • 任意のフィールドをレコードに追加する
    • ローカルのlet文に従ってフィールドの値を変更する
    • defvar変数を定義する
  5. すべてのフィールドをパスして,フィールド間の参照を解決する
  6. レコードをマスターレコードリストに追加する

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;
}

Yplus111なのは,add(Y, 1)が解決する前にlet Yが実行されるためである.

Using_Classes_as_Subroutines

Simple Valuesで説明したように,クラスは式の中で呼び出され,テンプレート引数を渡すことができる. これにより,TableGenはそのクラスを継承した新しい無名レコードを作成する. 通常通り,レコードはそのクラスで定義されたすべてのフィールドを受け取る.

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

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

CalcValueクラスは,テンプレート引数argと共に呼び出される. このクラスは,retフィールドの値を計算し,それをResultフィールドの初期化におけるpoint of callで取得する. この例で作成された匿名レコードは,結果の値を保持する以外の目的はない.

次に,実際の例を示す. isValidSizeクラスは,指定されたバイト数が有効なデータサイズであるかどうかを判断する. bit 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;
}

Preprocessing_Facilities

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

Appendix_Bang_Operators

bang演算子は,値の式の中で関数として機能する. bang演算子は1つまたは複数の引数を取り,それらを操作して結果を出す. 演算子が真偽値を生成する場合,結果の値は真なら1,偽なら0になる. 演算子が真偽値の引数をテストする場合,0は偽,0以外は真と解釈される.

!add(a, b, ...)

引数として与えられたa,b,...を加算し,結果を出力する.

!and(a, b, ...)

引数a,b,...に対してBit毎のAND演算を行い,結果を出力する. すべての引数が0または1の場合は論理AND演算となる.

!cast<type>(a)

aに対してtype型へのキャストを行い,結果を出力する. aが文字列でない場合,intbitの間,あるいはレコード型の間など,単純なキャストが行われる. これにより,レコードをクラスにキャストすることが出来る. レコードがstringにキャストされた場合,レコードの名前が出力される.

aが文字列の場合,それはレコード名として扱われ,すべての定義されたレコードのリストで検索される. 結果として得られるレコードは,指定された型であることが期待される.

例えば,マルチクラスの定義,またはマルチクラスの定義の中でインスタンス化されたクラスに!cast<type>(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ノードを作成する. 引数と名前の引数は,同じ長さのリストか,初期化されていない(?)でなければならない. names引数はlist<string>型でなければならない.

型システムの制限により,引数は共通の型を持つアイテムのリストでなければならない. 実際には,同じ型を持つか,共通の親クラスを持つレコードでなければならないことを意味する. dagとdagでないアイテムを混在させることはできない. ただし,?は使用できる.

!dag(op, [a1, a2, ?], ["name1", "name2", "name3"]) 

の結果は,

(op a1-value:$name1, a2-value:$name2, ?:$name3).

!empty(a)

stringlist,またはDAGaが空であれば1を,そうでなければ0を出力する. DAGが引数を持たない場合は空となる.

!eq(a, b)

aがbと等しい場合は1を,そうでない場合は0を出力する. 引数は,bitbitsintstringrecordのいずれか. 他のタイプのオブジェクトを比較するには,!cast<string>を使う.

!filter(var, list, predicate)

リスト内の要素をフィルタリングして新しいリストを作成する. フィルタリングを実行するために,TableGenは各要素に変数varをバインドし,次にvarを参照していると思われるpredicateを評価する. predicateは,bool値(bit,bits,intを生成しなければならない. その値は!ifと同様に解釈される. もしその値が0であれば,その要素は新しいリストに追加されない. もし,値が0以外であれば,その要素を新しいリストに追加する.

!find(string1, string2[, start])

string1の中からstring2を検索し,その位置を出力する. 検索の開始位置はstart1で指定することができ,0からstring1`の長さまでの範囲で指定することができる.

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

listの項目に対してfoldlを行う. 変数accはアキュムレータとして動作し,initで初期化される. 変数varlistの各要素に束縛される. 式は各要素で評価され,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)

各要素がsequence list/tagの対応する要素の関数となる新しいlist/tagを作成する. 関数を実行するために,TableGenは変数varを要素にバインドし,その後,式を評価する. 式は推定的に変数varを参照し,結果の値を計算する.

単純に,同じ値が複数回繰り返される一定の長さのリストを作成したい場合は,!listsplatを参照.

!ge(a, b)

ab以上の場合は1を,そうでない場合は0を出力する. 引数は,bitbitsintstringのいずれか.

!getdagop(dag) or !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ノードを作る時には,キャストは必要ない.

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

!gt(a, b)

この演算子は,abより大きい場合は1を,そうでない場合は0を出力する. 引数は,bitbitsintstringのいずれか.

!head(a)

list aの0番目の要素を生成する(!tailも参照)

!if(test, then, else)

bitまたはintを生成する必要のあるテストを評価する. 結果が0でない場合はthen式,そうでない場合はelse式が生成される.

!interleave(list, delim)

listのアイテムを連結し,各ペアの間にdelim文字列を挿入して,結果の文字列を出力する. listには,stringintbitsbitのリストを指定する. 空のlistの時は空の文字列になる. delimは,空の文字列でも良い.

!isa<type>(a)

aの型がtypeであれば1を,そうでなければ0を出力する.

!le(a, b)

ab以下であれば1を,そうでなければ0を出力する.

!listconcat(list1, list2, ...)

引数として与えられたlist1, list2, ...を連結し,その結果を出力する. それらのリストの要素は同じ型でなければならない.

!listsplat(value, count)

要素がすべてvalueに等しい長さcountのlistを出力する. 例: !listsplat(42, 3)[42, 42, 42]を出力する.

!lt(a, b)

abより小さければ1を,そうでなければ0を出力する.

!mul(a, b, ...)

a, b, ...を掛け合わせ,結果を出力する.

!ne(a, b)

abに等しくない場合は1を,そうでない場合は0を出力する. 引数は,bitbitsintstringrecordのいずれかの値. 他の型のオブジェクトを比較するには,!cast<string>を使う.

!not(a)

論理的なNOT演算を行う. 0を引数に渡すと,1(true)が,1を引数に渡すと,0(false)が出力される.

!op(a, b, ...)

a, b...に対してビット単位のORを行い,結果を出力する. すべての引数が0または1の場合は,論理的なORを行うことができる.

!setdagop(dag, po)

dagと同じ引数を持ち,その演算子がopに置き換えられたDAGノードを生成する

例: !setdagop((foo 1, 2), bar)の結果は,(bar 1, 2)となる.

!shl(a, count)

aを論理的にcount bitだけ左シフトし,その結果を出力する. 演算は64ビットの整数に対して行われ,0~63以外のcountでは結果は不定となる.

!size(a)

stringlist,またはdagであるaのサイズを出力する. DAGのサイズは,その引数の数であり,演算子はカウントされない.

!sra(a, count)

aを論理的にcount bitだけ右シフトし,その結果を出力する. 演算は64ビットの整数に対して行われ,0~63以外のcountでは結果は不定となる.

!strconcat(str1, str2, ...)

stringである引数str1, str2, ...を結合し,結果を出力する.

!sub(a, b)

引数として与えられたa,b,...を減算し,結果を出力する.

!subst(target, repl, value)

valueの中のtargetのすべての出現箇所をreplで置き換え,結果の値を出力する. valueは文字列でもよく,その場合は部分文字列置換が行われる.

この場合,演算子は,ターゲット・レコード名がバリュー・レコード名と等しい場合はreplレコードを生成し,そうでない場合はvalueを生成する.

!substr(string, start[, length])

与えられた文字列の部分文字列を抽出する. 部分文字列の開始位置はstartで指定され,0から文字列の長さまでの範囲で指定できる. 部分文字列の長さはlengthで指定され,指定されなければ文字列の残りの部分が抽出される. startlengthは整数でなければならない.

!tail(a)

list aの要素のうち,0番目の要素を除いたすべての要素を持つ新しいリストを出力する.

!xor(a, b, ...)

a,b, ...のビットごとのXOR演算を行い,その結果を出力する. 論理的なXORは,すべての引数が0または1の場合に実行できる.

Appendix_Paste_Operator_Examples

ここでは,レコード名にpaste演算子を使用した例を紹介する.

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 {
}

次に,フィールドの値,式でのpaste演算子の例を紹介する.

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

文字列フィールド式では,paste演算子の両側でsuffixを使用します. 左側では正常に評価されるが,右側ではそのままの形で評価される. 整数フィールド式では,some_ints変数の値とリテラルリストを使用している. 次のようなレコードが生成される.

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

Appendix_Sample_Record

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;
}

レコードの1行目を見ると,ADD32rrレコードが8つのクラスを継承していることがわかる. 継承階層は複雑だが,親クラスを使用することで,各命令に109個の個別フィールドを指定するよりもはるかに簡単になる.

以下は,ADD32rrをはじめとする複数のADD命令を定義するために使用されるコードである.

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

defm式は,ArithBinOp_RFがマルチクラスであり,BinOpRR_RFを継承した複数の具象レコード定義を含むことをTableGenに伝える. このクラスは,BinOpRRを継承し,BinOpRRITySchedを継承する,というようになっている. フィールドはすべての親クラスから継承されている. 例えば,IsIndirectBranchはInstructionクラスから継承されている.