TableGen 後端¶
簡介¶
TableGen 後端是 TableGen 功能的核心。原始碼檔案提供了類別和記錄,這些類別和記錄會被解析並最終成為記錄實例的集合,但後端的工作是以對使用者有意義的方式(通常是 C++ 標頭檔或警告、選項和錯誤訊息的文字列表)解釋和列印記錄。
LLVM、Clang 和 MLIR 都使用 TableGen,但目標非常不同。 LLVM 使用它來自動化產生大量關於指令、排程、核心和架構特性的資訊。有些後端產生的輸出會被多個原始碼檔案使用,因此它們的建立方式需要便於使用前處理器技巧。有些後端還可以列印 C++ 程式碼結構,以便可以直接原樣包含。
另一方面,Clang 主要將其用於診斷訊息(錯誤、警告、提示)和屬性,因此更偏向文字方面。
MLIR 使用 TableGen 來定義操作、操作方言和操作特性。
有關 TableGen 的深入描述,請參閱TableGen 程式設計師參考手冊,有關編寫新後端的指南,請參閱TableGen 後端開發人員指南。
LLVM 後端¶
警告
此部分尚未完成。以下每個章節都需要三個小節:目的描述(包含使用者列表)、從通用輸入產生的輸出,以及最終為何需要新後端(如果存在類似的後端)。
總體而言,每個後端都將採用相同的 TableGen 檔案類型,並轉換為適用於不同目標/用途的類似輸出。 TableGen 檔案、後端及其使用者之間存在隱含的契約。
例如,全域契約是每個後端都會產生巨集保護區段。根據檔案是由標頭檔還是原始碼檔案包含,甚至根據包含檔案的上下文,您必須在包含檔案之前定義巨集,才能獲得正確的輸出
#define GET_REGINFO_TARGET_DESC
#include "ARMGenRegisterInfo.inc"
並且只會包含產生檔案的一部分。如果您需要從同一個來源 TableGen 檔案以多種格式(實例化、初始化、getter/setter 函數等)取得相同的資訊,而無需多次重新編譯 TableGen 檔案,這會很有用。
有時,可能會在同一個包含檔案之前定義多個巨集,以輸出多個區塊
#define GET_REGISTER_MATCHER
#define GET_SUBTARGET_FEATURE_NAME
#define GET_MATCHER_IMPLEMENTATION
#include "ARMGenAsmMatcher.inc"
巨集在使用後將在包含檔案中自動取消定義。
在所有 LLVM 後端上,將在根 TableGen 檔案 <Target>.td
上執行 llvm-tblgen
二進制檔案,該檔案應包含所有其他檔案。這保證了所有需要的資訊都是可存取的,並且 TableGen 檔案中不需要重複。
程式碼發射器¶
目的:CodeEmitterGen 使用指令及其欄位的描述來建構自動化的程式碼發射器:一個函數,給定一個 MachineInstr,傳回指令的(目前為 32 位元無號)值。
輸出:C++ 程式碼,透過覆寫虛擬函數 <Target>CodeEmitter::function()
來實作目標的 CodeEmitter 類別。
用法:用於直接包含在 <Target>MCCodeEmitter.cpp
的末尾。
暫存器資訊¶
目的:此 tablegen 後端負責為程式碼產生器發射目標暫存器檔案的描述。它使用 Register、RegisterAliases 和 RegisterClass 類別的實例來收集此資訊。
輸出:C++ 程式碼,其中包含表示暫存器對應、屬性、遮罩等的列舉和結構。
用法:同時在 <Target>BaseRegisterInfo
和 <Target>MCTargetDesc
(標頭檔和原始碼檔案)上使用,並使用巨集定義它們用於宣告與初始化問題的用途。
指令資訊¶
目的:此 tablegen 後端負責為程式碼產生器發射目標指令集的描述。(與 CodeEmitter 有何不同?)
輸出:C++ 程式碼,其中包含表示指令對應、屬性、遮罩等的列舉和結構。
用法:同時在 <Target>BaseInstrInfo
和 <Target>MCTargetDesc
(標頭檔和原始碼檔案)上使用,並使用巨集定義它們用於宣告與初始化問題的用途。
組譯器編寫器¶
目的:為目前目標發射組譯程式列印器。
輸出:<Target>InstPrinter::printInstruction()
的實作,以及其他項目。
用法:直接包含在 InstPrinter/<Target>InstPrinter.cpp
中。
組譯器比對器¶
目的:發射目標指定符比對器,用於轉換 MCInst 結構中已解析的組譯運算元。它還發射用於自訂運算元解析的比對器。AsmMatcherEmitter.cpp
檔案中撰寫了詳盡的文件。
輸出:組譯器解析器的比對器函數、宣告等。
用法:用於後端的 AsmParser/<Target>AsmParser.cpp
中,以建構 AsmParser 類別。
反組譯器¶
目的:包含適用於各種架構的反組譯器表格發射器。DisassemblerEmitter.cpp
檔案中撰寫了詳盡的文件。
輸出:解碼表、靜態解碼函數等。
用法:直接包含在 Disassembler/<Target>Disassembler.cpp
中,以在所有手動解碼之後,處理所有預設解碼。
偽指令降低¶
目的:產生偽指令降低。
輸出:實作 <Target>AsmPrinter::emitPseudoExpansionLowering()
。
用法:直接包含在 <Target>AsmPrinter.cpp
中。
呼叫慣例¶
目的:負責發射此目標支援的呼叫慣例的描述。
輸出:實作靜態函數以處理透過比對樣式鏈結的呼叫慣例,在不比對時傳回 false。
用法:在 ISelLowering 和 FastIsel 中用作函數指標,指向 CC 選擇函數傳回的實作。
DAG 指令選擇¶
目的:產生 DAG 指令選擇器。
輸出:建立用於自動化 DAG 選擇的巨型函數。
用法:包含在 <Target>ISelDAGToDAG.cpp
中,位於目標的 SelectionDAGISel
實作內部。
DFA 封包化器¶
目的:此類別解析 Schedule.td 檔案,並產生可用於推斷是否可以在 VLIW 架構上的封包中新增指令的 API。此類別在內部產生確定性有限自動機 (DFA),該自動機將機器指令到功能單元的所有可能對應建模為在封包中新增指令。
輸出:GPU 後端(Hexagon、AMD)的排程表。
用法:直接包含在 <Target>InstrInfo.cpp
上。
快速指令選擇¶
目的:此 tablegen 後端發射程式碼,供「快速」指令選擇演算法使用。有關背景資訊,請參閱 lib/CodeGen/SelectionDAG/FastISel.cpp 頂部的註解。此檔案掃描目標的 tablegen 指令資訊檔案,並擷取具有明顯模式的指令,並發射程式碼以依類型和運算子查閱這些指令。
輸出:產生 Predicate
和 FastEmit
方法。
用法:實作目標 FastISel
類別實作的私有方法。
子目標¶
目的:產生子目標列舉。
輸出:用於子目標資訊的列舉、全域變數、本機表格。
用法:填充 <Target>Subtarget
和 MCTargetDesc/<Target>MCTargetDesc
檔案(標頭檔和原始碼)。
內建函數¶
目的:產生(目標)內建函數資訊。
選項解析器定義¶
目的:列印類別的列舉值。
可搜尋表格¶
目的:產生自訂可搜尋表格。
輸出:列舉、全域表格和查閱輔助函數。
用法:此後端允許從 TableGen 記錄產生自由格式、特定於目標的表格。 ARM 和 AArch64 目標使用此後端來產生系統暫存器表格;AMDGPU 目標使用它來產生關於複雜影像和記憶體緩衝區指令的中繼資料。
有關詳細描述,請參閱可搜尋表格參考。
X86EVEX2VEX¶
目的:此 X86 特定 tablegen 後端發射將 EVEX 編碼指令對應到其 VEX 編碼相同指令的表格。
Clang 後端¶
ClangAttrClasses¶
目的:建立 Attrs.inc,其中包含 Attr.td
中任何未設定 ASTNode = 0
的屬性的語意屬性類別宣告。此檔案包含在 Attr.h
中。
ClangAttrParserStringSwitches¶
目的:建立 AttrParserStringSwitches.inc,其中包含用於解析器相關字串切換的 StringSwitch::Case 語句。每個切換都有自己的巨集(例如 CLANG_ATTR_ARG_CONTEXT_LIST
或 CLANG_ATTR_IDENTIFIER_ARG_LIST
),預期在包含 AttrParserStringSwitches.inc 之前定義,並在之後取消定義。
ClangAttrImpl¶
目的:建立 AttrImpl.inc,其中包含 Attr.td
中任何未設定 ASTNode = 0
的屬性的語意屬性類別定義。此檔案包含在 AttrImpl.cpp
中。
ClangAttrList¶
目的:建立 AttrList.inc,當需要語意屬性識別碼列表時使用。例如,AttrKinds.h
包含此檔案以產生 attr::Kind
列舉值的列表。此列表分為多個類別:屬性、可繼承屬性和可繼承參數屬性。此分類根據 Attr.td
中的資訊自動發生,並用於實作 dyn_cast
和類似 API 所需的 classof
功能。
ClangAttrPCHRead¶
目的:建立 AttrPCHRead.inc,用於在 ASTReader::ReadAttributes
函數中還原序列化屬性。
ClangAttrPCHWrite¶
目的:建立 AttrPCHWrite.inc,用於在 ASTWriter::WriteAttributes
函數中序列化屬性。
ClangAttrSpellings¶
目的:建立 AttrSpellings.inc,用於實作 __has_attribute
功能測試巨集。
ClangAttrSpellingListIndex¶
目的:建立 AttrSpellingListIndex.inc,用於將已解析的屬性拼寫(包括使用的語法或範圍)對應到屬性拼寫列表索引。這些拼寫列表索引值是透過 AttributeList::getAttributeSpellingListIndex
公開的內部實作詳細資訊。
ClangAttrVisitor¶
目的:建立 AttrVisitor.inc,用於實作遞迴 AST 訪問器時。
ClangAttrTemplateInstantiate¶
目的:建立 AttrTemplateInstantiate.inc,其中實作 instantiateTemplateAttribute
函數,用於實例化需要複製屬性的模板時。
ClangAttrParsedAttrList¶
目的:建立 AttrParsedAttrList.inc,用於產生 AttributeList::Kind
已解析屬性列舉。
ClangAttrParsedAttrImpl¶
目的:建立 AttrParsedAttrImpl.inc,AttributeList.cpp
使用它來實作 AttributeList
類別上的多個函數。此功能透過 AttrInfoMap ParsedAttrInfo
陣列實作,該陣列包含每個已解析屬性物件的一個元素。
ClangAttrParsedAttrKinds¶
目的:建立 AttrParsedAttrKinds.inc,用於實作 AttributeList::getKind
函數,將字串(和語法)對應到已解析的屬性 AttributeList::Kind
列舉。
ClangAttrDump¶
目的:建立 AttrDump.inc,其中傾印關於屬性的資訊。它用於實作 ASTDumper::dumpAttr
。
ClangDiagsDefs¶
產生 Clang 診斷定義。
ClangDiagGroups¶
產生 Clang 診斷群組。
ClangDiagsIndexName¶
產生 Clang 診斷名稱索引。
ClangCommentNodes¶
產生 Clang AST 註解節點。
ClangDeclNodes¶
產生 Clang AST 宣告節點。
ClangStmtNodes¶
產生 Clang AST 語句節點。
ClangSACheckers¶
產生 Clang 靜態分析器檢查器。
ClangCommentHTMLNamedCharacterReferences¶
產生將具名字元參照轉換為 UTF-8 序列的函數。
ClangCommentCommandInfo¶
產生用於文件註解中命令的命令屬性。
ClangCommentCommandList¶
產生用於文件註解中的命令列表。
ArmNeon¶
為 clang 產生 arm_neon.h。
ArmNeonSema¶
為 clang 產生 ARM NEON sema 支援。
ArmNeonTest¶
為 clang 產生 ARM NEON 測試。
AttrDocs¶
目的:從 AttrDocs.td
建立 AttributeReference.rst
,並用於記錄使用者面向的屬性。
通用後端¶
列印記錄¶
TableGen 命令選項 --print-records
呼叫一個簡單的後端,該後端會列印原始碼檔案中定義的所有類別和記錄。這是預設的後端選項。有關更多資訊,請參閱TableGen 後端開發人員指南。
列印詳細記錄¶
TableGen 命令選項 --print-detailed-records
呼叫一個後端,該後端會列印原始碼檔案中定義的所有全域變數、類別和記錄,且比預設記錄列印器更詳細。有關更多資訊,請參閱TableGen 後端開發人員指南。
JSON 參考¶
目的:輸出每個 def
中的所有值,作為 JSON 資料結構,可以由各種語言輕鬆解析。適用於編寫自訂後端,而無需修改 TableGen 本身,或對傳遞到內建後端的相同 TableGen 資料執行輔助分析。
輸出:
輸出檔案的根目錄是一個 JSON 物件(即字典),包含以下固定鍵
!tablegen_json_version
:一個數字版本欄位,如果此資料的結構發生不相容的變更,則會增加。此處描述的格式對應於版本 1。!instanceof
:一個字典,其鍵是在 TableGen 輸入中定義的類別名稱。對於每個鍵,對應的值是一個字串陣列,其中提供從該類別衍生的def
記錄的名稱。因此,例如root["!instanceof"]["Instruction"]
會列出從Instruction
類別衍生的所有記錄的名稱。
對於每個 def
記錄,根物件也有一個記錄名稱的鍵。對應的值是一個子物件,其中包含以下固定鍵
!superclasses
:一個字串陣列,其中提供此記錄衍生的所有類別的名稱。!fields
:一個字串陣列,其中提供此記錄中所有使用field
關鍵字定義的變數的名稱。!name
:一個字串,提供記錄的名稱。這始終與 JSON 根物件中對應於此記錄字典的鍵相同。(如果記錄是匿名的,則名稱是任意的。)!anonymous
:一個布林值,指示記錄的名稱是否由 TableGen 輸入指定(如果為false
),或由 TableGen 本身發明(如果為true
)。!locs
:一個字串陣列,其中提供與此記錄關聯的來源位置。對於從multiclass
實例化的記錄,這會提供每個def
或defm
的位置,從最內層的multiclass
開始,到最上層的defm
結束。每個字串都包含檔案名稱和行號,以冒號分隔。
對於記錄中定義的每個變數,該記錄的 def
物件也有一個變數名稱的鍵。對應的值是使用以下描述的慣例將變數值轉換為 JSON。
某些 TableGen 資料類型會直接轉換為對應的 JSON 類型
完全未定義的值(例如,對於在此記錄的某些超類別中宣告但未初始化的變數,並且從未由此記錄本身或任何其他超類別初始化的變數)會發射為 JSON
null
值。int
和bit
值會發射為數字。請注意,TableGenint
值能夠容納太大而無法以 IEEE 雙精度精確表示的整數。 JSON 輸出中的整數常值將顯示完整的精確整數值。因此,如果您需要以完整精度檢索大型整數,則應使用能夠將此類常值轉換回 64 位元整數而不會遺失精度的 JSON 讀取器,例如 Python 的標準json
模組。string
和code
值會發射為 JSON 字串。任何元素類型
T
的list<T>
值都會發射為 JSON 陣列。陣列的每個元素都使用相同的慣例依序表示。bits
值也會發射為陣列。bits
陣列從最低有效位元到最高有效位元排序。因此,索引為i
的元素對應於 TableGen 來源中描述為x{i}
的位元。但是,請注意,這表示腳本語言可能會以與它在 TableGen 來源或診斷-print-records
輸出中顯示的相反順序顯示陣列。
所有其他 TableGen 的值類型都會被輸出成 JSON 物件,其中包含兩個標準欄位:kind
是一個識別符,用來描述物件所代表的值類型;而 printable
是一個字串,提供與 -print-records
中顯示的相同的值表示法。
指向
def
物件的參照具有kind=="def"
,並且有一個額外的欄位def
,提供所參照物件的名稱。指向同一個紀錄中另一個變數的參照具有
kind=="var"
,並且有一個額外的欄位var
,提供所參照變數的名稱。指向同一個紀錄中
bits
類型變數的特定位元的參照具有kind=="varbit"
,並且有兩個額外欄位:var
提供所參照變數的名稱,而index
提供位元的索引。類型為
dag
的值具有kind=="dag"
,並且有兩個額外欄位。operator
提供 dag 初始化器的左括號後面的初始值;args
是一個陣列,提供後續的引數。args
的元素是長度為 2 的陣列,提供每個引數的值,後面跟著它的冒號後綴名稱(如果有的話)。例如,在 dag 值(Op 22, "hello":$foo)
的 JSON 表示法中(假設Op
是在其他地方使用def
語句定義的紀錄名稱)operator
將會是一個物件,其中kind=="def"
且def=="Op"
args
將會是陣列[[22, null], ["hello", "foo"]]
。
如果在輸出中出現任何其他類型的值或複雜的表達式,它將會具有
kind=="complex"
,並且沒有額外的欄位。後端不應需要這些值。如有必要,可以使用標準的printable
欄位來提取它們在 TableGen 原始碼語法中的表示法。
SearchableTables 參考¶
TableGen 包含檔案 SearchableTable.td
提供了用於產生 C++ 可搜尋表格的類別。這些表格在以下章節中描述。若要產生 C++ 程式碼,請使用 --gen-searchable-tables
選項執行 llvm-tblgen
,這會調用後端,從您提供的紀錄中產生表格。
為可搜尋表格產生的每個資料結構都受到 #ifdef
的保護。這允許您包含產生的 .inc
檔案,並僅選擇某些資料結構包含在內。以下範例顯示了這些保護中使用的巨集名稱。
通用列舉類型¶
GenericEnum
類別讓定義 C++ 列舉類型和該類型的列舉元素變得容易。若要定義類型,請定義一個父類別為 GenericEnum
且名稱為所需列舉類型的紀錄。此類別提供三個欄位,您可以使用 let
語句在紀錄中設定這些欄位。
string FilterClass
。列舉類型將為每個衍生自此類別的紀錄擁有一個元素。這些紀錄會被收集起來以組裝完整的元素集合。string NameField
。在收集的紀錄中,指定元素名稱的欄位名稱。如果紀錄沒有這樣的欄位,則將使用紀錄的名稱。string ValueField
。在收集的紀錄中,指定元素數值的欄位名稱。如果紀錄沒有這樣的欄位,則將分配一個整數值。值會以字母順序從 0 開始分配。
以下範例中,元素的值被明確指定,作為 BEntry
類別的範本引數。顯示了產生的 C++ 程式碼。
def BValues : GenericEnum {
let FilterClass = "BEntry";
let NameField = "Name";
let ValueField = "Encoding";
}
class BEntry<bits<16> enc> {
string Name = NAME;
bits<16> Encoding = enc;
}
def BFoo : BEntry<0xac>;
def BBar : BEntry<0x14>;
def BZoo : BEntry<0x80>;
def BSnork : BEntry<0x4c>;
#ifdef GET_BValues_DECL
enum BValues {
BBar = 20,
BFoo = 172,
BSnork = 76,
BZoo = 128,
};
#endif
在以下範例中,元素的值會自動分配。請注意,值是從 0 開始分配,並依元素名稱的字母順序排列。
def CEnum : GenericEnum {
let FilterClass = "CEnum";
}
class CEnum;
def CFoo : CEnum;
def CBar : CEnum;
def CBaz : CEnum;
#ifdef GET_CEnum_DECL
enum CEnum {
CBar = 0,
CBaz = 1,
CFoo = 2,
};
#endif
通用表格¶
GenericTable
類別用於定義可搜尋的通用表格。TableGen 產生 C++ 程式碼來定義表格條目,並產生函數的宣告和定義,以根據主鍵搜尋表格。若要定義表格,請定義一個父類別為 GenericTable
且名稱為條目全域表格名稱的紀錄。此類別提供六個欄位。
string FilterClass
。表格將為每個衍生自此類別的紀錄擁有一個條目。string FilterClassField
。這是FilterClass
的選用欄位,應為 bit 類型。如果指定,則只有此欄位為 true 的紀錄才會在表格中具有對應的條目。如果此欄位未包含在Fields
列表中,則不會包含在產生的 C++ 欄位中。string CppTypeName
。保存條目的表格的 C++ struct/class 類型名稱。如果未指定,則使用FilterClass
名稱。list<string> Fields
。在收集的紀錄中,包含表格條目資料的欄位名稱列表。此列表的順序決定了 C++ 初始化器中值的順序。請參閱下文以獲取有關這些欄位類型的資訊。list<string> PrimaryKey
。組成主鍵的欄位列表。string PrimaryKeyName
。產生的 C++ 函數名稱,該函數對主鍵執行查找。bit PrimaryKeyEarlyOut
。請參閱下面的第三個範例。bit PrimaryKeyReturnRange
。當設定為 1 時,修改查找函數的定義以返回結果範圍,而不是指向物件的單一指標。當多個物件符合查找函數指定的條件時,此功能證明很有用。目前,它僅支援主要查找函數。有關更多詳細資訊,請參閱下面的第二個範例。
TableGen 嘗試推斷每個表格欄位的類型,以便它可以格式化發射表格中的 C++ 初始化器。它可以推斷 bit
、bits<n>
、string
、Intrinsic
和 Instruction
。這些可以在主鍵中使用。任何其他欄位類型都必須明確指定;這如下面的第二個範例所示。此類欄位不能在主鍵中使用。
欄位類型的一個特殊情況與程式碼有關。任意程式碼由字串表示,但必須作為不帶引號的 C++ 初始化器發射。如果程式碼欄位是使用程式碼字面值 ([{...}]
) 定義的,則 TableGen 將知道在發射時不帶引號。但是,如果是使用字串字面值或複雜的字串表達式定義的,則 TableGen 將不知道。在這種情況下,您可以通過在 GenericTable
紀錄中包含以下行來強制 TableGen 將該欄位視為程式碼,其中 *xxx* 是程式碼欄位名稱。
string TypeOf_xxx = "code";
以下範例中,TableGen 可以推斷欄位類型。請注意,表格條目紀錄是匿名的;條目紀錄的名稱無關緊要。
def ATable : GenericTable {
let FilterClass = "AEntry";
let FilterClassField = "IsNeeded";
let Fields = ["Str", "Val1", "Val2"];
let PrimaryKey = ["Val1", "Val2"];
let PrimaryKeyName = "lookupATableByValues";
}
class AEntry<string str, int val1, int val2, bit isNeeded> {
string Str = str;
bits<8> Val1 = val1;
bits<10> Val2 = val2;
bit IsNeeded = isNeeded;
}
def : AEntry<"Bob", 5, 3, 1>;
def : AEntry<"Carol", 2, 6, 1>;
def : AEntry<"Ted", 4, 4, 1>;
def : AEntry<"Alice", 4, 5, 1>;
def : AEntry<"Costa", 2, 1, 1>;
def : AEntry<"Dale", 2, 1, 0>;
以下是產生的 C++ 程式碼。lookupATableByValues
的宣告受到 GET_ATable_DECL
的保護,而定義受到 GET_ATable_IMPL
的保護。
#ifdef GET_ATable_DECL
const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2);
#endif
#ifdef GET_ATable_IMPL
constexpr AEntry ATable[] = {
{ "Costa", 0x2, 0x1 }, // 0
{ "Carol", 0x2, 0x6 }, // 1
{ "Ted", 0x4, 0x4 }, // 2
{ "Alice", 0x4, 0x5 }, // 3
{ "Bob", 0x5, 0x3 }, // 4
/* { "Dale", 0x2, 0x1 }, // 5 */ // We don't generate this line as `IsNeeded` is 0.
};
const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2) {
struct KeyType {
uint8_t Val1;
uint16_t Val2;
};
KeyType Key = { Val1, Val2 };
auto Table = ArrayRef(ATable);
auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
[](const AEntry &LHS, const KeyType &RHS) {
if (LHS.Val1 < RHS.Val1)
return true;
if (LHS.Val1 > RHS.Val1)
return false;
if (LHS.Val2 < RHS.Val2)
return true;
if (LHS.Val2 > RHS.Val2)
return false;
return false;
});
if (Idx == Table.end() ||
Key.Val1 != Idx->Val1 ||
Key.Val2 != Idx->Val2)
return nullptr;
return &*Idx;
}
#endif
ATable
中的表格條目依 Val1
排序,並且在每個 Val1
值內,依 Val2
排序。這允許對表格進行二元搜尋,該搜尋在查找函數中由 std::lower_bound
執行。查找函數返回對找到的表格條目的參照,如果未找到任何條目,則返回空指標。如果表格具有單一主鍵欄位,該欄位是整數且密集編號,則會產生直接查找而不是二元搜尋。
此範例包含一個 TableGen 無法推斷其類型的欄位。Kind
欄位使用上面定義的列舉類型 CEnum
。為了通知 TableGen 類型,衍生自 GenericTable
的紀錄必須包含一個名為 TypeOf_
field 的字串欄位,其中 *field* 是需要其類型的欄位名稱。
def CTable : GenericTable {
let FilterClass = "CEntry";
let Fields = ["Name", "Kind", "Encoding"];
string TypeOf_Kind = "CEnum";
let PrimaryKey = ["Encoding"];
let PrimaryKeyName = "lookupCEntryByEncoding";
}
class CEntry<string name, CEnum kind, int enc> {
string Name = name;
CEnum Kind = kind;
bits<16> Encoding = enc;
}
def : CEntry<"Apple", CFoo, 10>;
def : CEntry<"Pear", CBaz, 15>;
def : CEntry<"Apple", CBar, 13>;
以下是產生的 C++ 程式碼。
#ifdef GET_CTable_DECL
const CEntry *lookupCEntryByEncoding(uint16_t Encoding);
#endif
#ifdef GET_CTable_IMPL
constexpr CEntry CTable[] = {
{ "Apple", CFoo, 0xA }, // 0
{ "Apple", CBar, 0xD }, // 1
{ "Pear", CBaz, 0xF }, // 2
};
const CEntry *lookupCEntryByEncoding(uint16_t Encoding) {
struct KeyType {
uint16_t Encoding;
};
KeyType Key = { Encoding };
auto Table = ArrayRef(CTable);
auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
[](const CEntry &LHS, const KeyType &RHS) {
if (LHS.Encoding < RHS.Encoding)
return true;
if (LHS.Encoding > RHS.Encoding)
return false;
return false;
});
if (Idx == Table.end() ||
Key.Encoding != Idx->Encoding)
return nullptr;
return &*Idx;
}
在上述範例中,讓我們新增一個紀錄,其編碼與紀錄 CEntry<"Pear", CBaz, 15>
相同。
def CFoobar : CEnum;
def : CEntry<"Banana", CFoobar, 15>;
以下是新的產生 CTable
#ifdef GET_Table_IMPL
constexpr CEntry Table[] = {
{ "Apple", CFoo, 0xA }, // 0
{ "Apple", CBar, 0xD }, // 1
{ "Banana", CFoobar, 0xF }, // 2
{ "Pear", CBaz, 0xF }, // 3
};
由於 Banana
在詞彙上首先出現,因此在 CEntry
表格中,名稱為 Banana
的紀錄將在名稱為 Pear
的紀錄之前。因此,即使在某些情況下正確的結果可能是名稱為 Pear
的紀錄,lookupCEntryByEncoding
函數也將始終返回指向名稱為 Banana
的紀錄的指標。這種情況使得現有的查找函數不足,因為它們始終返回指向表格中單一條目的指標,但實際上它應該返回一系列結果,因為多個條目符合查找函數尋找的條件。在這種情況下,需要修改查找函數的定義以返回一系列結果,這可以通過設定 PrimaryKeyReturnRange
來完成。
def CTable : GenericTable {
let FilterClass = "CEntry";
let Fields = ["Name", "Kind", "Encoding"];
string TypeOf_Kind = "CEnum";
let PrimaryKey = ["Encoding"];
let PrimaryKeyName = "lookupCEntryByEncoding";
let PrimaryKeyReturnRange = true;
}
以下是修改後的查找函數。
llvm::iterator_range<const CEntry *> lookupCEntryByEncoding(uint16_t Encoding) {
struct KeyType {
uint16_t Encoding;
};
KeyType Key = {Encoding};
struct Comp {
bool operator()(const CEntry &LHS, const KeyType &RHS) const {
if (LHS.Encoding < RHS.Encoding)
return true;
if (LHS.Encoding > RHS.Encoding)
return false;
return false;
}
bool operator()(const KeyType &LHS, const CEntry &RHS) const {
if (LHS.Encoding < RHS.Encoding)
return true;
if (LHS.Encoding > RHS.Encoding)
return false;
return false;
}
};
auto Table = ArrayRef(Table);
auto It = std::equal_range(Table.begin(), Table.end(), Key, Comp());
return llvm::make_range(It.first, It.second);
}
新的查找函數將返回一個迭代器範圍,其中第一個指標指向第一個結果,最後一個指標指向表格中最後一個匹配的結果。但是,請注意,僅 PrimaryKeyName
支援發射修改後的定義。
PrimaryKeyEarlyOut
欄位,當設定為 1 時,會修改查找函數,使其測試主鍵的第一個欄位,以確定它是否在收集的紀錄主鍵範圍內。如果不是,則函數返回空指標,而不執行二元搜尋。這對於僅為較大基於列舉的空間的某些元素提供資料的表格很有用。主鍵的第一個欄位必須是整數類型;它不能是字串。
將 let PrimaryKeyEarlyOut = 1
新增到上面的 ATable
def ATable : GenericTable {
let FilterClass = "AEntry";
let Fields = ["Str", "Val1", "Val2"];
let PrimaryKey = ["Val1", "Val2"];
let PrimaryKeyName = "lookupATableByValues";
let PrimaryKeyEarlyOut = 1;
}
會導致查找函數如下變更
const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2) {
if ((Val1 < 0x2) ||
(Val1 > 0x5))
return nullptr;
struct KeyType {
...
我們可以建構兩個具有相同 FilterClass
的 GenericTable,以便它們從相同的整體紀錄集中選擇,但為它們分配不同的 FilterClassField
值,以便它們包含該類別紀錄的不同子集。
例如,我們可以建立兩個僅包含偶數或奇數紀錄的表格。欄位 IsEven
和 IsOdd
不會包含在產生的 C++ 欄位中,因為它們未包含在 Fields
列表中。
class EEntry<bits<8> value> {
bits<8> Value = value;
bit IsEven = !eq(!and(value, 1), 0);
bit IsOdd = !not(IsEven);
}
foreach i = {1-10} in {
def : EEntry<i>;
}
def EEntryEvenTable : GenericTable {
let FilterClass = "EEntry";
let FilterClassField = "IsEven";
let Fields = ["Value"];
let PrimaryKey = ["Value"];
let PrimaryKeyName = "lookupEEntryEvenTableByValue";
}
def EEntryOddTable : GenericTable {
let FilterClass = "EEntry";
let FilterClassField = "IsOdd";
let Fields = ["Value"];
let PrimaryKey = ["Value"];
let PrimaryKeyName = "lookupEEntryOddTableByValue";
}
產生的表格是
constexpr EEntry EEntryEvenTable[] = {
{ 0x2 }, // 0
{ 0x4 }, // 1
{ 0x6 }, // 2
{ 0x8 }, // 3
{ 0xA }, // 4
};
constexpr EEntry EEntryOddTable[] = {
{ 0x1 }, // 0
{ 0x3 }, // 1
{ 0x5 }, // 2
{ 0x7 }, // 3
{ 0x9 }, // 4
};
搜尋索引¶
SearchIndex
類別用於為通用表格定義額外的查找函數。若要定義額外的函數,請定義一個父類別為 SearchIndex
且名稱為所需查找函數名稱的紀錄。此類別提供三個欄位。
GenericTable Table
。要接收另一個查找函數的表格名稱。list<string> Key
。組成次要鍵的欄位列表。bit EarlyOut
。請參閱通用表格中的第三個範例。
以下是新增到上述 CTable
的次要鍵範例。產生的函數根據 Name
和 Kind
欄位查找條目。
def lookupCEntry : SearchIndex {
let Table = CTable;
let Key = ["Name", "Kind"];
}
使用 SearchIndex
會產生以下額外的 C++ 程式碼。
const CEntry *lookupCEntry(StringRef Name, unsigned Kind);
...
const CEntry *lookupCEntryByName(StringRef Name, unsigned Kind) {
struct IndexType {
const char * Name;
unsigned Kind;
unsigned _index;
};
static const struct IndexType Index[] = {
{ "APPLE", CBar, 1 },
{ "APPLE", CFoo, 0 },
{ "PEAR", CBaz, 2 },
};
struct KeyType {
std::string Name;
unsigned Kind;
};
KeyType Key = { Name.upper(), Kind };
auto Table = ArrayRef(Index);
auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
[](const IndexType &LHS, const KeyType &RHS) {
int CmpName = StringRef(LHS.Name).compare(RHS.Name);
if (CmpName < 0) return true;
if (CmpName > 0) return false;
if ((unsigned)LHS.Kind < (unsigned)RHS.Kind)
return true;
if ((unsigned)LHS.Kind > (unsigned)RHS.Kind)
return false;
return false;
});
if (Idx == Table.end() ||
Key.Name != Idx->Name ||
Key.Kind != Idx->Kind)
return nullptr;
return &CTable[Idx->_index];
}