1 TableGen 程式設計師參考¶
1.1 簡介¶
TableGen 的目的是根據來源檔案中的資訊產生複雜的輸出檔案,這些來源檔案比輸出檔案更容易編碼,也更容易長期維護和修改。資訊以宣告式風格編碼,其中包含類別和記錄,然後由 TableGen 處理。內部化的記錄會傳遞給各種後端,後端從記錄的子集中提取資訊,並產生一個或多個輸出檔案。這些輸出檔案通常是 C++ 的 .inc
檔案,但也可能是後端開發人員需要的任何類型的檔案。
本文詳細介紹了 LLVM TableGen 工具。它適用於使用 TableGen 為專案產生程式碼的程式設計師。如果您正在尋找簡單的概觀,請查看 TableGen 概觀。用於調用 TableGen 的各種 *-tblgen
命令在 tblgen 家族 - 從描述到 C++ 程式碼 中描述。
後端的一個範例是 RegisterInfo
,它為特定的目標機器產生暫存器檔案資訊,供 LLVM 獨立於目標的程式碼產生器使用。請參閱 TableGen 後端 以取得 LLVM TableGen 後端的描述,並參閱 TableGen 後端開發人員指南 以取得撰寫新後端的指南。
以下是後端可以執行的一些操作。
為特定的目標機器產生暫存器檔案資訊。
為目標產生指令定義。
產生程式碼產生器用於將指令與中繼表示 (IR) 節點匹配的模式。
為 Clang 產生語意屬性識別符。
為 Clang 產生抽象語法樹 (AST) 宣告節點定義。
為 Clang 產生 AST 語句節點定義。
1.1.1 概念¶
TableGen 原始檔包含兩個主要項目:抽象記錄和具體記錄。在本文件和其他 TableGen 文件中,抽象記錄稱為類別。(這些類別與 C++ 類別不同,也不會對應到 C++ 類別。)此外,具體記錄通常簡稱為記錄,儘管有時術語記錄同時指類別和具體記錄。區別在上下文中應該很清楚。
類別和具體記錄都有唯一的名稱,可以由程式設計師選擇或由 TableGen 產生。與該名稱關聯的是具有值的欄位列表和可選的父類別列表(有時稱為基類或超類別)。欄位是後端將處理的主要資料。請注意,TableGen 不會為欄位指定任何意義;意義完全取決於後端和包含這些後端輸出的程式。
注意
術語「父類別」可以指作為另一個類別的父代的類別,也可以指具體記錄繼承自的類別。術語的這種非標準使用方式之所以出現,是因為 TableGen 以類似的方式處理類別和具體記錄。
後端處理 TableGen 解析器建構的具體記錄的某個子集,並發出輸出檔案。這些檔案通常是 C++ .inc
檔案,由需要這些記錄中資料的程式包含。但是,後端可以產生任何類型的輸出檔案。例如,它可以產生一個資料檔案,其中包含標記有識別符和替換參數的訊息。在像 LLVM 程式碼產生器這樣複雜的用例中,可能有很多具體記錄,其中一些記錄可能具有出乎意料的大量欄位,從而導致大型輸出檔案。
為了降低 TableGen 檔案的複雜性,類別用於抽象化記錄欄位群組。例如,一些類別可以抽象化機器暫存器檔案的概念,而其他類別可以抽象化指令格式,還有一些類別可以抽象化個別指令。TableGen 允許任意類別層次結構,因此兩個概念的抽象類別可以共用第三個超類別,該超類別從兩個原始概念中抽象化出常見的「子概念」。
為了使類別更有用,具體記錄(或另一個類別)可以將類別請求為父類別,並將範本引數傳遞給它。這些範本引數可以用於父類別的欄位中,以自訂方式初始化它們。也就是說,記錄或類別 A
可以使用一組範本引數請求父類別 S
,而記錄或類別 B
可以使用不同的一組引數請求 S
。如果沒有範本引數,則需要更多類別,每個範本引數組合都需要一個類別。
類別和具體記錄都可以包含未初始化的欄位。未初始化的「值」以問號 (?
) 表示。類別通常具有未初始化的欄位,這些欄位預期在具體記錄繼承這些類別時填寫。即便如此,具體記錄的某些欄位可能仍然保持未初始化狀態。
TableGen 提供multiclass以在一個位置收集一組記錄定義。Multiclass 是一種巨集,可以「調用」以一次定義多個具體記錄。Multiclass 可以從其他 multiclass 繼承,這表示 multiclass 會繼承其父 multiclass 中的所有定義。
附錄 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 有兩種字串字面值
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 後端一起使用,在該後端中,它用於區分一般記錄欄位與編碼欄位。
1.3.3 驚嘆號運算子¶
TableGen 提供「驚嘆號運算子」,它們具有廣泛的用途
BangOperator ::= one of !add !and !cast !con !dag !div !empty !eq !exists !filter !find !foldl !foreach !ge !getdagarg !getdagname !getdagop !gt !head !if !initialized !interleave !isa !le !listconcat !listflatten !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
運算子的語法與其他驚嘆號運算子略有不同,因此它被單獨定義
CondOperator ::= !cond
請參閱 附錄 A:驚嘆號運算子 以取得每個驚嘆號運算子的描述。
1.3.4 包含檔案¶
TableGen 具有包含機制。包含檔案的內容在詞法上取代 include
指令,然後被解析,就像它最初在主檔案中一樣。
IncludeDirective ::= "include" TokString
主檔案和包含檔案的各個部分可以使用預處理器指令進行條件化。
PreprocessorDirective ::= "#define" | "#ifdef" | "#ifndef"
1.4 類型¶
TableGen 語言是靜態類型化的,使用簡單但完整的類型系統。類型用於檢查錯誤、執行隱式轉換以及幫助介面設計人員約束允許的輸入。每個值都必須具有關聯的類型。
TableGen 支援低階類型(例如,bit
)和高階類型(例如,dag
)的混合。這種彈性允許您方便且簡潔地描述各種記錄。
Type ::= "bit" | "int" | "string" | "dag" | "code" | "bits" "<"TokInteger
">" | "list" "<"Type
">" |ClassID
ClassID ::=TokIdentifier
bit
bit
是一個布林值,可以是 0 或 1。int
int
類型表示一個簡單的 64 位元整數值,例如 5 或 -42。string
string
類型表示任意長度的字元有序序列。code
關鍵字
code
是string
的別名,可用於指示作為程式碼的字串值。bits<
n>
bits
類型是任意長度 n 的固定大小整數,被視為單獨的位元。可以單獨存取這些位元。此類型的欄位適用於表示指令操作碼、暫存器編號或定址模式/暫存器/位移。欄位的位元可以單獨或作為子欄位設定。例如,在指令位址中,可以單獨設定定址模式、基底暫存器編號和位移。list<
type>
此類型表示一個列表,其元素為角括號中指定的類型。元素類型是任意的;它甚至可以是另一個列表類型。列表元素從 0 開始編號。
dag
此類型表示節點的可巢狀有向無環圖 (DAG)。每個節點都有一個運算子和零個或多個引數(或運算元)。引數可以是另一個
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
,而不是「1」、「-」和「5」。不建議使用連字號作為範圍標點符號。
1.5.1 簡單值¶
SimpleValue
具有多種形式。
SimpleValue ::=SimpleValue1
|SimpleValue2
|SimpleValue3
|SimpleValue4
|SimpleValue5
|SimpleValue6
|SimpleValue7
|SimpleValue8
|SimpleValue9
SimpleValue1 ::=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:驚嘆號運算子 以取得每個驚嘆號運算子的描述。
Type 僅適用於某些驚嘆號運算子,並且不得為 code
。
1.5.2 後綴值¶
上面描述的 SimpleValue
值可以使用某些後綴指定。後綴的目的是取得主要值的子值。以下是某些主要值的可能後綴。
- value
{17}
最終值是整數值的位元 17(請注意大括號)。
- value
{8...15}
最終值是整數值的位元 8–15。可以透過指定
{15...8}
來反轉位元的順序。- value
[i]
最終值是列表值的元素 i(請注意方括號)。換句話說,方括號充當列表的下標運算子。僅當指定單個元素時才是這種情況。
- value
[i,]
最終值是一個列表,其中包含列表的單個元素 i。簡而言之,具有單個元素的列表切片。
- value
[4...7,17,2...3,4]
最終值是一個新列表,它是列表值的切片。新列表包含元素 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 最終值是指定記錄值中指定欄位的值。
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
}*]
包含 MultiClassID
的 ParentClassList
僅在 defm
陳述式的類別列表中有效。在這種情況下,ID 必須是 multiclass 的名稱。
引數值可以用兩種形式指定
位置引數 (
value
)。該值會被賦值給對應位置的引數。對於Foo<a0, a1>
,a0
將被賦值給第一個引數,而a1
將被賦值給第二個引數。具名引數 (
name=value
)。該值會被賦值給具有指定名稱的引數。對於Foo<a=a0, b=a1>
,a0
將被賦值給名稱為a
的引數,而a1
將被賦值給名稱為b
的引數。
必要引數也可以指定為具名引數。
請注意,無論使用哪種方式(具名或位置)指定,引數都只能指定一次,並且位置引數應放在具名引數之前。
Body ::= ";" | "{"BodyItem
* "}" BodyItem ::=Type
TokIdentifier
["="Value
] ";" | "let"TokIdentifier
["{"RangeList
"}"] "="Value
";" | "defvar"TokIdentifier
"="Value
";" |Assert
主體中的欄位定義指定要包含在類別或紀錄中的欄位。如果未指定初始值,則欄位的值將未初始化。必須指定類型;TableGen 不會從值推斷類型。
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
子句,從一個或多個類別繼承。父類別中的所有欄位都會新增到紀錄中。如果兩個或多個父類別提供相同的欄位,則紀錄最終會使用最後一個父類別的欄位值。
作為特殊情況,紀錄的名稱可以作為模板引數傳遞給該紀錄的父類別。例如
class A <dag d> {
dag the_dag = d;
}
def rec1 : A<(ops rec1)>;
DAG (ops rec1)
作為模板引數傳遞給類別 A
。請注意,DAG 包含 rec1
,即正在定義的紀錄。
建立新紀錄所採取的步驟有些複雜。請參閱紀錄的建置方式。
有關範例,請參閱範例:類別和紀錄。
1.6.3 範例:類別和紀錄¶
以下是一個簡單的 TableGen 檔案,其中包含一個類別和兩個紀錄定義。
class C {
bit V = true;
}
def X : C;
def Y : C {
let V = false;
string Greeting = "Hello!";
}
首先,定義了抽象類別 C
。它有一個名為 V
的欄位,該欄位是一個位元,初始化為 true。
接下來,定義了兩個紀錄,它們從類別 C
衍生而來;也就是說,以 C
作為它們的父類別。因此,它們都繼承了 V
欄位。紀錄 Y
還定義了另一個字串欄位 Greeting
,它初始化為 "Hello!"
。此外,Y
覆寫了繼承的 V
欄位,將其設定為 false。
類別對於將多個紀錄的共同特徵隔離在一個位置非常有用。類別可以將通用欄位初始化為預設值,但從該類別繼承的紀錄可以覆寫預設值。
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
類別的目的是充當一種列舉類型。它提供一個單一欄位 Value
,它保存一個 3 位元的數字。它的模板引數 val
用於設定 Value
欄位。八個紀錄中的每一個都定義了 FPFormat
作為其父類別。列舉值在角括號中作為模板引數傳遞。每個紀錄都將繼承具有適當列舉值的 Value
欄位。
以下是一個更複雜的帶有模板引數的類別範例。首先,我們定義一個類似於上述 FPFormat
類別的類別。它採用一個模板引數,並使用它來初始化名為 Value
的欄位。然後,我們定義四個紀錄,它們繼承具有四個不同整數值的 Value
欄位。
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
欄位的兩個位元。我們可以定義一個類別,它接受 ModRefVal
紀錄作為模板引數,並將其值拆分為兩個欄位,每個欄位一位元。然後我們可以定義從 ModRefBits
繼承的紀錄,從而從它取得兩個欄位,每個欄位對應於作為模板引數傳遞的 ModRefVal
紀錄中的一個位元。
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
陳述式通常很有用。以下是兩個範例。請注意,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
— 定義多個紀錄¶
雖然帶有模板引數的類別是分解多個紀錄之間共性的好方法,但 multiclass 允許一種方便的方法一次定義多個紀錄。例如,考慮一個 3 位址指令架構,其指令有兩種格式:reg = reg op reg
和 reg = reg op imm
(例如,SPARC)。我們希望在一個地方指定這兩種常見格式的存在,然後在另一個地方指定所有運算是什麼。multiclass
和 defm
陳述式實現了此目標。您可以將 multiclass 視為巨集或模板,它會展開為多個紀錄。
MultiClass ::= "multiclass"TokIdentifier
[TemplateArgList
]ParentClassList
"{"MultiClassStatement
+ "}" MultiClassID ::=TokIdentifier
MultiClassStatement ::=Assert
|Def
|Defm
|Defvar
|Foreach
|If
|Let
與常規類別一樣,multiclass 具有名稱,並且可以接受模板引數。multiclass 可以從其他 multiclass 繼承,這會導致其他 multiclass 展開並貢獻於繼承 multiclass 中的紀錄定義。multiclass 的主體包含一系列陳述式,這些陳述式使用 Def
和 Defm
定義紀錄。此外,Defvar
、Foreach
和 Let
陳述式可用於分解更多通用元素。If
和 Assert
陳述式也可以使用。
與常規類別一樣,multiclass 具有隱含的模板引數 NAME
(請參閱NAME)。當在 multiclass 中定義具名(非匿名)紀錄,且紀錄的名稱不包含模板引數 NAME
的使用時,這種使用會自動前置到名稱。也就是說,以下在 multiclass 內部是等效的
def Foo ...
def NAME # Foo ...
當 multiclass 由 multiclass 定義之外的 defm
陳述式「實例化」或「調用」時,會在 multiclass 中建立定義的紀錄。multiclass 中的每個 def
陳述式都會產生一個紀錄。與頂層 def
陳述式一樣,這些定義可以從多個父類別繼承。
有關範例,請參閱範例:multiclass 和 defm。
1.6.6 defm
— 調用 multiclass 以定義多個紀錄¶
一旦定義了 multiclass,您就可以使用 defm
陳述式來「調用」它們並處理這些 multiclass 中的多個紀錄定義。這些紀錄定義由 multiclass 中的 def
陳述式指定,並間接由 defm
陳述式指定。
Defm ::= "defm" [NameValue
]ParentClassList
";"
選填的 NameValue
以與 def
的名稱相同的方式形成。ParentClassList
是一個冒號,後跟至少一個 multiclass 和任意數量的常規類別。multiclass 必須位於常規類別之前。請注意,defm
沒有主體。
此陳述式實例化所有指定 multiclass 中定義的所有紀錄,無論是直接透過 def
陳述式,還是間接透過 defm
陳述式。這些紀錄也接收在父類別列表中包含的任何常規類別中定義的欄位。這對於向 defm
建立的所有紀錄新增一組通用欄位非常有用。
名稱以 def
使用的相同特殊模式解析。如果未包含名稱,則會提供未指定但全域唯一的名稱。也就是說,以下範例最終會得到不同的名稱
defm : SomeMultiClass<...>; // A globally unique name.
defm "" : SomeMultiClass<...>; // An empty name.
defm
陳述式可以在 multiclass 主體中使用。發生這種情況時,第二種變體等效於
defm NAME : SomeMultiClass<...>;
更一般而言,當 defm
出現在 multiclass 中,並且其名稱不包含隱含模板引數 NAME
的使用時,則會自動前置 NAME
。也就是說,以下在 multiclass 內部是等效的
defm Foo : SomeMultiClass<...>;
defm NAME # Foo : SomeMultiClass<...>;
有關範例,請參閱範例:multiclass 和 defm。
1.6.7 範例:multiclass 和 defm¶
以下是一個使用 multiclass
和 defm
的簡單範例。考慮一個 3 位址指令架構,其指令有兩種格式:reg = reg op reg
和 reg = reg op imm
(立即值)。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
multiclass 的每次使用都會定義兩個紀錄,一個帶有 _rr
後綴,另一個帶有 _ri
後綴。回想一下,使用 multiclass 的 defm
的名稱會前置到該 multiclass 中定義的紀錄的名稱。因此,產生的定義被命名為
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
可以在 multiclass 中使用,以「調用」其他 multiclass 並建立這些 multiclass 中定義的紀錄,以及目前 multiclass 中定義的紀錄。在以下範例中,basic_s
和 basic_p
multiclass 包含引用 basic_r
multiclass 的 defm
陳述式。basic_r
multiclass 僅包含 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
multiclass,五個來自 basic_p
multiclass
ADDSSrr, ADDSSrm
ADDSDrr, ADDSDrm
ADDX
ADDPSrr, ADDPSrm
ADDPDrr, ADDPDrm
ADDY
defm
陳述式(無論是在頂層還是在 multiclass 中)都可以從常規類別以及 multiclass 繼承。規則是常規類別必須列在 multiclass 之後,並且必須至少有一個 multiclass。
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;
此範例將建立四個紀錄,此處按字母順序顯示及其欄位。
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 };
}
也可以在 multiclass 內部使用 let
陳述式,提供另一種方法來分解紀錄的共性,尤其是在使用多個層級的 multiclass 實例化時。
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<
類別>
,其中類別是某個紀錄類別。defset
陳述式為其陳述式建立一個範圍。在 defset
的範圍內定義的紀錄不是類別類型是錯誤的。
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
陳述式將輸入字串印出到標準錯誤輸出。它旨在用於除錯目的。
在頂層,訊息會立即印出。
在紀錄/類別/multiclass 內,dump 會在包含紀錄的每個實例化點評估。
Dump ::= "dump" Value
";"
Value
是任意字串表達式。例如,它可以與 !repr 結合使用,以調查傳遞給 multiclass 的值
multiclass MC<dag s> {
dump "s = " # !repr(s);
}
1.6.13 if
— 根據測試選擇陳述式¶
if
陳述式允許根據表達式的值選擇兩組陳述式群組中的其中一組。
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"Value
","Value
";"
第一個 Value
是一個布林條件。如果為真,則陳述式不執行任何操作。如果條件為假,則印出非致命錯誤訊息。第二個 Value
是一個訊息,它可以是任意字串表達式。它包含在錯誤訊息中作為註解。assert
陳述式的確切行為取決於其位置。
在頂層,斷言會立即檢查。
在紀錄定義中,陳述式會被儲存,並且在紀錄完全建立後檢查所有斷言。
在類別定義中,斷言會被儲存,並由所有繼承自該類別的子類別和紀錄繼承。然後在紀錄完全建立時檢查斷言。
在多重類別定義中,斷言與多重類別的其他組件一起儲存,然後每次使用
defm
實例化多重類別時都會檢查斷言。
在 TableGen 檔案中使用斷言可以簡化 TableGen 後端的紀錄檢查。以下是在兩個類別定義中使用 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 節點由一個運算元和零或多個引數(或運算元)組成。每個引數可以是任何所需的類型。通過使用另一個 DAG 節點作為引數,可以建立 DAG 節點的任意圖。
dag
實例的語法為
(
運算元 引數1,
引數2,
…)
運算元必須存在,並且必須是一個紀錄。可以有零或多個引數,以逗號分隔。運算元和引數可以有三種格式。
格式 |
意義 |
---|---|
value |
引數值 |
value |
引數值和關聯的名稱 |
name |
具有未設定(未初始化)值的引數名稱 |
value 可以是任何 TableGen 值。name(如果存在)必須是 TokVarName
,它以錢字號 ($
) 開頭。名稱的目的是用特定含義標記 DAG 中的運算元或引數,或將一個 DAG 中的引數與另一個 DAG 中同名的引數關聯起來。
以下 bang 運算子對於使用 DAG 非常有用:!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,假結果值將為 0。當運算子測試布林引數時,它將 0 解釋為假,將非 0 解釋為真。
警告
!getop
和 !setop
bang 運算子已被棄用,建議使用 !getdagop
和 !setdagop
。
!add(
a,
b,
…)
此運算子將 a、b 等相加,並產生總和。
!and(
a,
b,
…)
此運算子對 a、b 等執行位元 AND 運算,並產生結果。如果所有引數都是 0 或 1,則可以執行邏輯 AND 運算。當最左邊的運算元為 0 時,此運算子會短路為 0。
!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 沒有引數,則 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 表達式,該表達式據推測會參考 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
,其中每個元素都是 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 的第零個元素。(另請參閱
!tail
。)!if(
test,
then,
else)
此運算子評估 test,它必須產生
bit
或int
。如果結果不是 0,則產生 then 表達式;否則產生 else 表達式。!initialized(
a)
如果 a 不是未初始化的值 (
?
),則此運算子產生 1,否則產生 0。!interleave(
list,
delim)
此運算子串連 list 中的項目,在每對之間交錯 delim 字串,並產生結果字串。列表可以是字串、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 等,並產生結果列表。列表必須具有相同的元素型別。
!listflatten(
list)
此運算子平坦化列表的列表 list,並產生一個列表,其中包含所有組成列表的元素串連在一起。如果 list 的型別為
list<list<X>>
,則結果列表的型別為list<X>
。如果 list 的元素型別不是列表,則結果是 list 本身。!listremove(
list1,
list2)
此運算子返回 list1 的副本,移除也出現在 list2 中的所有元素。列表必須具有相同的元素型別。
!listsplat(
value,
count)
這個運算子會產生一個長度為 count 的列表,其所有元素都等於 value。例如,
!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
或 record 值。使用!cast<string>
來比較其他類型的物件。!not(
a)
這個運算子對 a 執行邏輯 NOT 運算,a 必須是整數。引數 0 會產生 1(真);任何其他引數都會產生 0(假)。
!or(
a,
b, ...)
這個運算子對 a、b 等執行位元 OR 運算,並產生結果。如果所有引數都是 0 或 1,則可以執行邏輯 OR 運算。如果最左邊的運算元是 -1,這個運算子會短路為 -1(全為 1)。
!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 表示為字串。不保證 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* 可以是 record 名稱,在這種情況下,如果 *target* record 名稱等於 *value* record 名稱,則運算子會產生 *repl* record;否則產生 *value*。
!substr(
string,
start[,
length])
這個運算子會擷取給定 *string* 的子字串。子字串的起始位置由 *start* 指定,其範圍介於 0 和字串長度之間。子字串的長度由 *length* 指定;如果未指定,則擷取字串的其餘部分。*start* 和 *length* 引數必須是整數。
!tail(
a)
這個運算子會產生一個新列表,其中包含列表 *a* 的所有元素,但排除索引為零的元素。(另請參閱
!head
。)!tolower(
a)
這個運算子將字串輸入 *a* 轉換為小寫。
!toupper(
a)
這個運算子將字串輸入 *a* 轉換為大寫。
!xor(
a,
b, ...)
這個運算子對 a、b 等執行位元互斥 OR 運算,並產生結果。如果所有引數都是 0 或 1,則可以執行邏輯互斥 OR 運算。
1.11 附錄 B:Paste 運算子範例¶
以下範例說明如何在 record 名稱中使用 paste 運算子。
defvar suffix = "_suffstring";
defvar some_ints = [0, 1, 2, 3];
def name # suffix {
}
foreach i = [1, 2] in {
def rec # i {
}
}
第一個 def
沒有使用 suffix
變數的值。第二個 def 有使用 i
迭代器變數的值,因為它不是全域名稱。產生以下 records。
def namesuffix {
}
def rec1 {
}
def rec2 {
}
以下是第二個範例,說明如何在欄位值運算式中使用 paste 運算子。
def test {
string strings = suffix # suffix;
list<int> integers = some_ints # [4, 5, 6];
}
strings
欄位運算式在 paste 運算子的兩側都使用了 suffix
。它在左側正常評估,但在右側逐字採用。integers
欄位運算式使用了 some_ints
變數的值和一個文字列表。產生以下 record。
def test {
string strings = "_suffstringsuffix";
list<int> ints = [0, 1, 2, 3, 4, 5, 6];
}
1.12 附錄 C:範例 Record¶
LLVM 支援的目標機器之一是 Intel x86。以下來自 TableGen 的輸出顯示了為表示 32 位元暫存器到暫存器 ADD 指令而建立的 record。
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;
}
在 record 的第一行,您可以看到 ADD32rr
record 繼承自八個類別。儘管繼承層次結構很複雜,但使用父類別比為每個指令指定 109 個個別欄位要簡單得多。
以下是用於定義 ADD32rr
和多個其他 ADD
指令的程式碼片段
defm ADD : ArithBinOp_RF<0x00, 0x02, 0x04, "add", MRM0r, MRM0m,
X86add_flag, add, 1, 1, 1>;
defm
陳述式告訴 TableGen,ArithBinOp_RF
是一個 multiclass,其中包含多個繼承自 BinOpRR_RF
的具體 record 定義。該類別又繼承自 BinOpRR
,而 BinOpRR
又繼承自 ITy
和 Sched
,依此類推。欄位繼承自所有父類別;例如,IsIndirectBranch
繼承自 Instruction
類別。