TableGen 概觀¶
簡介¶
TableGen 的目的是協助人們開發和維護特定領域資訊的紀錄。由於可能存在大量的這些紀錄,它被特別設計為允許編寫彈性的描述,並將這些紀錄的共同特徵提取出來。這減少了描述中的重複量,降低了出錯的機率,並使組織特定領域的資訊更加容易。
TableGen 前端解析檔案,實例化宣告,並將結果交給特定領域的 後端 進行處理。 有關 TableGen 的深入描述,請參閱 TableGen 程式設計師參考手冊。 有關執行 TableGen 各種變體的 *-tblgen
命令的詳細資訊,請參閱 tblgen - 從描述到 C++ 程式碼。
TableGen 目前的主要使用者是 LLVM 目標獨立程式碼產生器 和 Clang 診斷和屬性。
請注意,如果您經常使用 TableGen 並使用 emacs 或 vim,您可以在 LLVM 發行版本的 llvm/utils/emacs
和 llvm/utils/vim
目錄中找到 emacs “TableGen 模式” 和 vim 語言檔案。
TableGen 程式¶
TableGen 檔案由 TableGen 程式 llvm-tblgen 解譯,該程式位於您的建置目錄下的 bin 中。 它沒有安裝在系統中(或您設定的 sysroot 位置),因為它在 LLVM 的建置過程之外沒有用途。
執行 TableGen¶
TableGen 的執行方式與任何其他 LLVM 工具相同。第一個(可選)參數指定要讀取的檔案。如果未指定檔名,則 llvm-tblgen
從標準輸入讀取。
為了使其有用,必須使用 後端 之一。 這些後端可以在命令列上選擇(輸入 ‘llvm-tblgen -help
’ 以取得列表)。 例如,要取得繼承特定類型的所有定義的列表(這對於建立這些紀錄的列舉列表很有用),請使用 -print-enums
選項
$ llvm-tblgen X86.td -print-enums -class=Register
AH, AL, AX, BH, BL, BP, BPL, BX, CH, CL, CX, DH, DI, DIL, DL, DX, EAX, EBP, EBX,
ECX, EDI, EDX, EFLAGS, EIP, ESI, ESP, FP0, FP1, FP2, FP3, FP4, FP5, FP6, IP,
MM0, MM1, MM2, MM3, MM4, MM5, MM6, MM7, R10, R10B, R10D, R10W, R11, R11B, R11D,
R11W, R12, R12B, R12D, R12W, R13, R13B, R13D, R13W, R14, R14B, R14D, R14W, R15,
R15B, R15D, R15W, R8, R8B, R8D, R8W, R9, R9B, R9D, R9W, RAX, RBP, RBX, RCX, RDI,
RDX, RIP, RSI, RSP, SI, SIL, SP, SPL, ST0, ST1, ST2, ST3, ST4, ST5, ST6, ST7,
XMM0, XMM1, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15, XMM2, XMM3, XMM4, XMM5,
XMM6, XMM7, XMM8, XMM9,
$ llvm-tblgen X86.td -print-enums -class=Instruction
ABS_F, ABS_Fp32, ABS_Fp64, ABS_Fp80, ADC32mi, ADC32mi8, ADC32mr, ADC32ri,
ADC32ri8, ADC32rm, ADC32rr, ADC64mi32, ADC64mi8, ADC64mr, ADC64ri32, ADC64ri8,
ADC64rm, ADC64rr, ADD16mi, ADD16mi8, ADD16mr, ADD16ri, ADD16ri8, ADD16rm,
ADD16rr, ADD32mi, ADD32mi8, ADD32mr, ADD32ri, ADD32ri8, ADD32rm, ADD32rr,
ADD64mi32, ADD64mi8, ADD64mr, ADD64ri32, ...
預設後端會印出所有紀錄。 還有一個通用後端,它將所有紀錄輸出為 JSON 資料結構,使用 -dump-json 選項啟用。
如果您計劃使用 TableGen,您很可能需要編寫一個 後端,以提取您需要的特定資訊並以適當的方式格式化它。 您可以透過在 C++ 中擴展 TableGen 本身,或透過使用任何可以消耗 JSON 輸出的語言編寫腳本來完成此操作。
範例¶
在沒有其他參數的情況下,llvm-tblgen 解析指定的檔案並印出所有類別,然後是所有定義。 這是查看各種定義完全展開後的樣子的好方法。 在 X86.td
檔案上執行此操作會印出以下內容(在撰寫本文時)
...
def ADD32rr { // Instruction X86Inst I
string Namespace = "X86";
dag OutOperandList = (outs GR32:$dst);
dag InOperandList = (ins GR32:$src1, GR32:$src2);
string AsmString = "add{l}\t{$src2, $dst|$dst, $src2}";
list<dag> Pattern = [(set GR32:$dst, (add GR32:$src1, GR32:$src2))];
list<Register> Uses = [];
list<Register> Defs = [EFLAGS];
list<Predicate> Predicates = [];
int CodeSize = 3;
int AddedComplexity = 0;
bit isReturn = 0;
bit isBranch = 0;
bit isIndirectBranch = 0;
bit isBarrier = 0;
bit isCall = 0;
bit canFoldAsLoad = 0;
bit mayLoad = 0;
bit mayStore = 0;
bit isImplicitDef = 0;
bit isConvertibleToThreeAddress = 1;
bit isCommutable = 1;
bit isTerminator = 0;
bit isReMaterializable = 0;
bit isPredicable = 0;
bit hasDelaySlot = 0;
bit usesCustomInserter = 0;
bit hasCtrlDep = 0;
bit isNotDuplicable = 0;
bit hasSideEffects = 0;
InstrItinClass Itinerary = NoItinerary;
string Constraints = "";
string DisableEncoding = "";
bits<8> Opcode = { 0, 0, 0, 0, 0, 0, 0, 1 };
Format Form = MRMDestReg;
bits<6> FormBits = { 0, 0, 0, 0, 1, 1 };
ImmType ImmT = NoImm;
bits<3> ImmTypeBits = { 0, 0, 0 };
bit hasOpSizePrefix = 0;
bit hasAdSizePrefix = 0;
bits<4> Prefix = { 0, 0, 0, 0 };
bit hasREX_WPrefix = 0;
FPFormat FPForm = ?;
bits<3> FPFormBits = { 0, 0, 0 };
}
...
此定義對應於 x86 架構的 32 位元暫存器-暫存器 add
指令。 def ADD32rr
定義了一個名為 ADD32rr
的紀錄,並且行尾的註解指示了定義的超類別。 紀錄的主體包含 TableGen 為該紀錄組裝的所有資料,指示該指令是 “X86” 命名空間的一部分,模式指示程式碼產生器如何選擇該指令,它是一個雙位址指令,具有特定的編碼等等。 紀錄中資訊的內容和語義特定於 X86 後端的需求,僅作為範例顯示。
如您所見,程式碼產生器支援的每個指令都需要大量資訊,並且手動指定所有資訊將是無法維護的、容易出錯的,並且一開始就令人厭倦。 因為我們正在使用 TableGen,所以所有資訊都來自以下定義
let Defs = [EFLAGS],
isCommutable = 1, // X = ADD Y,Z --> X = ADD Z,Y
isConvertibleToThreeAddress = 1 in // Can transform into LEA.
def ADD32rr : I<0x01, MRMDestReg, (outs GR32:$dst),
(ins GR32:$src1, GR32:$src2),
"add{l}\t{$src2, $dst|$dst, $src2}",
[(set GR32:$dst, (add GR32:$src1, GR32:$src2))]>;
此定義使用了自訂類別 I
(從自訂類別 X86Inst
擴展而來),該類別在 X86 特定的 TableGen 檔案中定義,以提取其類別指令共有的共同特徵。 TableGen 的一個關鍵特徵是,它允許最終使用者定義他們在描述資訊時喜歡使用的抽象概念。
語法¶
TableGen 的語法鬆散地基於 C++ 模板,具有內建類型和規範。 此外,TableGen 的語法引入了一些自動化概念,例如 multiclass、foreach、let 等。
基本概念¶
TableGen 檔案由兩個主要部分組成:「類別」和「定義」,兩者都被視為「紀錄」。
TableGen 紀錄 具有唯一的名稱、值列表和超類別列表。 值列表是 TableGen 為每個紀錄建立的主要資料; 它保存了應用程式的特定領域資訊。 此資料的解釋留給特定的 後端,但結構和格式規則由 TableGen 負責並固定。
TableGen 定義 是「紀錄」的具體形式。 這些通常沒有任何未定義的值,並以 ‘def
’ 關鍵字標記。
def FeatureFPARMv8 : SubtargetFeature<"fp-armv8", "HasFPARMv8", "true",
"Enable ARMv8 FP">;
在此範例中,FeatureFPARMv8 是使用某些值初始化的 SubtargetFeature
紀錄。 類別的名稱透過 class 關鍵字在同一檔案或某些其他包含的檔案中定義。 大多數目標 TableGen 檔案都包含 include/llvm/Target
中的通用檔案。
TableGen 類別 是用於建立和描述其他紀錄的抽象紀錄。 這些類別允許最終使用者為他們目標鎖定的領域(例如 LLVM 程式碼產生器中的 “Register”、“RegisterClass” 和 “Instruction”)或為實作者建立抽象概念,以幫助提取紀錄的共同屬性(例如 “FPInst”,用於表示 X86 後端中的浮點指令)。 TableGen 會追蹤用於建立定義的所有類別,因此後端可以找到特定類別(例如 “Instruction”)的所有定義。
class ProcNoItin<string Name, list<SubtargetFeature> Features>
: Processor<Name, NoItineraries, Features>;
在此,類別 ProcNoItin 接收類型為 string 的參數 Name 和目標功能列表,透過向下傳遞參數以及硬式編碼 NoItineraries 來專門化 Processor 類別。
TableGen 多重類別 是同時實例化的一組抽象紀錄。 每個實例化都可能產生多個 TableGen 定義。 如果一個多重類別繼承自另一個多重類別,則子多重類別中的定義將成為當前多重類別的一部分,就好像它們是在當前多重類別中宣告的一樣。
multiclass ro_signed_pats<string T, string Rm, dag Base, dag Offset, dag Extend,
dag address, ValueType sty> {
def : Pat<(i32 (!cast<SDNode>("sextload" # sty) address)),
(!cast<Instruction>("LDRS" # T # "w_" # Rm # "_RegOffset")
Base, Offset, Extend)>;
def : Pat<(i64 (!cast<SDNode>("sextload" # sty) address)),
(!cast<Instruction>("LDRS" # T # "x_" # Rm # "_RegOffset")
Base, Offset, Extend)>;
}
defm : ro_signed_pats<"B", Rm, Base, Offset, Extend,
!foreach(decls.pattern, address,
!subst(SHIFT, imm_eq0, decls.pattern)),
i8>;
有關 TableGen 的深入描述,請參閱 TableGen 程式設計師參考手冊。
TableGen 後端¶
沒有後端,TableGen 檔案就沒有實際意義。 執行 *-tblgen
時的預設操作是以文字格式印出資訊,但這僅適用於偵錯 TableGen 檔案本身。 然而,TableGen 的強大之處在於將原始檔解譯為內部表示,可以將其產生為您想要的任何內容。
TableGen 目前的用法是建立包含表格的巨型標頭檔,您可以直接包含它們(如果輸出是您正在編寫程式碼的語言),或者透過環繞檔案包含的巨集在預處理中使用。
如果後端已經以 C 格式印出表格,或者如果輸出只是一個字串列表(用於錯誤和警告訊息),則可以使用直接輸出。 如果相同的資訊需要在不同的上下文中使用(例如指令名稱),則應使用預處理後的輸出,因此您的後端應印出一個元資訊列表,該列表可以塑造成不同的編譯時格式。
有關可用後端的列表,請參閱 TableGen 後端,有關如何編寫和偵錯新後端的資訊,請參閱 TableGen 後端開發人員指南。
工具與資源¶
除了本文檔之外,TableGen 的工具和資源列表可以在 TableGen 的 README 中找到。
TableGen 的不足之處¶
儘管 TableGen 非常通用,但它仍存在一些已被多次指出的不足之處。 共同的主題是,雖然 TableGen 允許您建立特定領域語言,但您建立的最終語言缺乏其他 DSL 的功能,這反過來又大大增加了 TableGen 檔案的大小和複雜性。
同時,TableGen 允許您透過客製化的後端來建立基本概念的幾乎任何含義,這可能會扭曲原始設計,並使新手很難理解難以捉摸的 TableGen 檔案。
有些人贊成進一步擴展語義,但要確保後端遵守嚴格的規則。 其他人則建議我們應該轉向更少、更強大的、針對特定目的設計的 DSL,甚至重複使用現有的 DSL。