之前寫tablegen的介紹寫到一半放棄了, 最近培訓需要再拉出來看看, 順便做個筆記.
1. 語法介紹
官方文檔見這里, 以下是文檔的翻譯.
1.1. 類型系統
tablegen是強類型語言, 其類型系統同時包含low-level(i.e. bit int)與high-level(i.e. dag). 以下是tablegen支持的內建類型.
bit: 布爾值, 0或1.
int: 表示32bit整型.
string: 字符串.
code: 一個代碼片段, 通常以多行字符串的形式表示.
bits
list
class type: class類型.
dag: 表示一個有向圖.
1.2. 值與表達式
tablengen支持以下表達式.
?: 未初始化值.
0b10: bit串, 注意該位串不會被擴展或截斷, 因此對類型為bits
1: 十進制常量.
0x11: 十六進制常量.
"abc": 字符串常量, 可以賦值給string類型或code類型.
value: 引用一個value.
value{n}: 訪問value的第n位.
value.field: 訪問value的field成員.
[x, y, z]: 隊列容器賦值.
{a, b}: 位串賦值, 其總長度為位串a加位串b的長度和.
還有很多表達式就不一一列出了, 讀者請自行參考文檔. 另外tablegen支持的內建函數表達式(i.e. foreach strconcat listconcat)也請參見文檔說明.
1.3. 類(class)與定義(definition)
類與定義是tablegen語言中關鍵組成, 它們又被稱作條目(records).
一個records包含一個唯一的名字, 一組成員變量及一組(擴展的)超類. 對records的解釋由具體的后端處理, 但records的語法格式與結構由tablegen的前端進行檢查.
類是一種抽象的records, 類定義為用戶提供抽象架構公共屬性的能力. 類似的, multiclass提供了一組抽象的records, 對multiclass的實例化會創建一組定義.
定義是一種具體的records, 它們通過def關鍵字被定義.
來看個例子加深理解.
class C { bit V = 1; }
def X : C;
def Y : C {
string Greeting = "hello";
}
以上代碼片段聲明了類C, 類C有兩個實例, X與Y. 由於定義X與定義Y均繼承自類C, 所以它們都包含成員V, 而定義Y包含一個額外的字符串成員Greeting.
1.4. 賦值表達式
let表達式允許我們修改變量的值.
class C { bit V = 0; }
def D : C { let V = 1; }
類C中定義了一個bit類型變量V, 其默認值為0, 在實例D中將V修改為1.
1.5. 類的模板參數
tablegen提供了參數化類定義, 允許在定義類時指定一系列參數並綁定到對應變量.
class C<bit v> { bit V = v; }
def X : C<1>;
def Y : C<0>;
def Z : C<1> { let V = 0; }
以上文例子為例, 類C是一個模板類, 其接受一個bit類型變量v並使用該值初始化其成員變量V.
定義X與定義Y均繼承自類C, 根據傳入的參數, 定義X是一個V為1的類實例(等於1.4.中的D), 定義Y是一個V為0的類實例.
特殊的, 定義Z中的V值為0還是為1呢? 在模板初始化時V被設置為1, 然后let表達式生效, 所以V最終為0 (可以理解為賦值操作在模板實例化之后生效).
1.5. multiclass定義與實例
模板類給我們提供了描述一組具有公共屬性的類的能力, 進一步的tablegen還提供了一種定義多個類的方式.
multiclass允許我們同時定義一組類, 繼承自multiclass類會被擴展成多個類定義實例. 讓我們來看個例子.
class C<bit v, bit i> { bit V = v; bit I = i; }
multiclass D<bit v> {
def X : C<v, 0>;
def Y : C<v, 1>;
}
defm V0 : D<0>;
defm V1 : D<1>;
模板類C接受兩個參數v與i, 即有四種類實例. 如果使用之前的寫法就需要編寫四個類定義.
利用multiclass我們定義一個類D, 當實例化一個類D時我們實際實例化了兩個類定義X與Y. 通過這種方式我們可以進一步減少重復代碼的編寫.
注意: 實例化一個multiclass時需要使用defm替換def, 並且該實例的名字是defm后接的名字與multiclass中def的名字拼接而成. i.e. 上文中實際定義了四個類實例, 名字分別為V0_X, V0_Y, V1_X, V1_Y.
從以上舉例可以看到tablegen是非常靈活的語言, 易於使用者編寫.
2. 語言參考
這里介紹如何編寫tablegen代碼.
2.1. 注釋
使用//或/.../方式添加注釋.
2.2. 文件包含
使用include "***"方式包含其它文件
TODO
3. tablegen后端
前文提到過tablegen由兩部分組成, 前端的parser的作用是將tablegen description file (td file)翻譯成records, records的含義則由后端解析.
當前clang與llvm都用到tablegen來生成代碼, 前端部分不很熟悉, 因此本文簡要介紹下后端自動生成代碼.
這里可以了解tablegen自動生成的代碼模塊.
3.1. tablegen源碼目錄
tablegen代碼由兩部分組成, 前端的lexer與parser在lib/TableGen/目錄下, 作用是解析td file.
后端代碼在utils/TableGen/目錄下, 是我們關心的邏輯代碼. 在解析每個模塊時我們會一一介紹.
3.2. 如何使用
這塊之前有做過簡要介紹. 這次以RISCV架構為例:
[00:58:41] hansy@hansy:~/llvm-mono/llvm (master)$ llvm-tblgen -I lib/Target/RISCV/ -I ./include/ ./lib/Target/RISCV/RISCV.td -o 123.inc
[00:59:36] hansy@hansy:~/llvm-mono/llvm (master)$ vim 123.inc
llvm-tblgen是tablegen源碼編譯出的工具, 它必須接受一個td file輸入(RISCV.td), 該td file中包含的頭文件索引路徑通過-I參數傳遞, 默認不添加參數時會生成全量代碼, 這里就不展示了.
這里再稍微提下llvm構建時是如何調用tablegen的. 首先在lib/Target/RISCV/CMakeLists.txt中包含如下代碼:
set(LLVM_TARGET_DEFINITIONS RISCV.td)
tablegen(LLVM RISCVGenAsmMatcher.inc -gen-asm-matcher)
tablegen(LLVM RISCVGenAsmWriter.inc -gen-asm-writer)
tablegen(LLVM RISCVGenCompressInstEmitter.inc -gen-compress-inst-emitter)
tablegen(LLVM RISCVGenDAGISel.inc -gen-dag-isel)
tablegen(LLVM RISCVGenDisassemblerTables.inc -gen-disassembler)
tablegen(LLVM RISCVGenGlobalISel.inc -gen-global-isel)
tablegen(LLVM RISCVGenInstrInfo.inc -gen-instr-info)
tablegen(LLVM RISCVGenMCCodeEmitter.inc -gen-emitter)
tablegen(LLVM RISCVGenMCPseudoLowering.inc -gen-pseudo-lowering)
tablegen(LLVM RISCVGenRegisterBank.inc -gen-register-bank)
tablegen(LLVM RISCVGenRegisterInfo.inc -gen-register-info)
tablegen(LLVM RISCVGenSubtargetInfo.inc -gen-subtarget)
tablegen(LLVM RISCVGenSystemOperands.inc -gen-searchable-tables)
其中函數tablegen (defined in cmake/modules/TableGen.cmake)作用是根據傳入參數配置命令行並調用llvm-tablegen. 可以看到生成的代碼放在build/lib/Target/RISCV/目錄下.
由於不同架構的不同硬件特性, 需要生成的代碼也不相同, 下文會簡要介紹幾個常見的文件, 細節的內容可以根據以上介紹自行查找代碼.
3.3. 指令描述(-gen-instr-info)
TODO