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 後端上,llvm-tblgen
二進制文件將在根 TableGen 文件 <Target>.td
上執行,該文件應包含所有其他文件。這保證了所有需要的資訊都可訪問,並且 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 選擇函數返回的實作。
DAGISel¶
目的:產生 DAG 指令選擇器。
輸出:建立用於自動化 DAG 選擇的巨型函數。
使用方法:包含在目標的 SelectionDAGISel
實作內的 <Target>ISelDAGToDAG.cpp
中。
DFAPacketizer¶
目的:此類別會解析 Schedule.td 檔案,並產生一個 API,可用於推斷是否可以將指令添加到 VLIW 架構上的封包中。該類別在內部產生一個確定性有限自動機 (DFA),該自動機在將指令添加到封包時對機器指令到功能單元的所有可能映射進行建模。
輸出:GPU 後端(Hexagon、AMD)的排程表。
使用方法:直接包含在 <Target>InstrInfo.cpp
上。
FastISel¶
目的:此 tablegen 後端發出用於“快速”指令選擇演算法的程式碼。有關背景資訊,請參閱 lib/CodeGen/SelectionDAG/FastISel.cpp 頂部的註解。此檔案會掃描目標的 tablegen 指令資訊檔案並提取具有明顯模式的指令,並發出程式碼以按類型和運算符查找這些指令。
輸出:產生 Predicate
和 FastEmit
方法。
使用方法:實作目標的 FastISel
類別實作的私有方法。
子目標¶
目的:產生子目標列舉。
輸出:用於子目標資訊的列舉、全域變數、區域表格。
用法:填充 <Target>Subtarget
和 MCTargetDesc/<Target>MCTargetDesc
檔案(包括標頭檔和原始碼檔)。
Intrinsic¶
用途:產生(目標)內建函數資訊。
OptParserDefs¶
用途:列印類別的列舉值。
SearchableTables¶
用途:產生自訂的可搜尋表格。
輸出:列舉、全域表格和查詢輔助函數。
用法:此後端允許從 TableGen 記錄產生自由格式、目標特定的表格。ARM 和 AArch64 目標使用此後端產生系統暫存器表格;AMDGPU 目標使用它產生關於複雜影像和記憶體緩衝區指令的中繼資料。
如需詳細說明,請參閱SearchableTables 參考。
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 語意分析支援。
ArmNeonTest¶
為 clang 產生 ARM NEON 測試。
AttrDocs¶
用途:從 AttrDocs.td
建立 AttributeReference.rst
,用於記錄面向使用者的屬性。
通用後端¶
列印記錄¶
TableGen 命令選項 --print-records
會呼叫一個簡單的後端,用於列印原始程式碼中定義的所有類別和記錄。這是預設的後端選項。如需更多資訊,請參閱 TableGen 後端開發人員指南。
列印詳細記錄¶
TableGen 命令選項 --print-detailed-records
會呼叫一個後端程式,以比預設記錄印表機更詳細的方式印出原始碼中定義的所有全域變數、類別和記錄。如需更多資訊,請參閱《TableGen 後端開發者指南》。
JSON 參考¶
目的:以 JSON 資料結構輸出每個 def
中的所有值,以便各種語言輕鬆解析。適用於編寫自訂後端程式,而無需修改 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 源碼語法中的表示形式。
可搜索表格參考¶
TableGen include 檔案 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++ 結構/類別類型的名稱。如果未指定,則使用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
的順序排序,並在每個值中按 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
構造兩個 GenericTables,以便它們從相同的整體記錄集中進行選擇,但為它們分配不同的 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
。 請參閱 通用表格 中的第三個範例。bit ReturnRange
。 請參閱 通用表格 中的第二個範例。
以下是在上面 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];
}