LLVM 之后端篇(1):零基础快速入门 TableGen
Author: stormQ
Created: Friday, 06. August 2021 11:12PM
Last Modified: Wednesday, 11. August 2021 10:40PM
本文基于release/12.x
版本的 LLVM 源码,介绍了TableGen
的基本概念、操纵命令以及一些常见的词法和语法。从而,初步了解TableGen
以便进一步研究 LLVM 后端。
TableGen
是一种由 LLVM 团队开发的声明式编程语言,用于描述编译器多个阶段中所使用的信息。比如:描述不同的 Clang 诊断、描述不同的静态分析检查器等。通过不同的TableGen
处理工具,这些信息可以进一步转换成对 Clang 诊断、Clang 驱动器、Clang 静态分析器、代码生成器等有实际含义的特定信息。
TableGen
可用于描述 LLVM 后端中各种特定于目标的信息,比如:指令格式、指令、寄存器、模式匹配有向无环图、指令选择匹配顺序、调用约定和目标 CPU 属性等。从而,易于维护 LLVM 后端的代码。
TableGen
源文件是 ASCII 文本文件,习惯上以.td
结尾。
在 LLVM 12.0.0 版本中,TableGen
处理工具包括以下几种:clang-tblgen
、lldb-tblgen
、llvm-tblgen
和mlir-tblgen
。
llvm-tblgen
用于处理描述 LLVM 后端的TableGen
文件。其常用的命令如下:
命令格式 | 作用 | 示例 |
---|---|---|
llvm-tblgen -print-records <input-file> -o <output-file> |
打印所有的记录(包括抽象记录和具体记录) | llvm-tblgen -print-records X86.td -I ../../../include |
llvm-tblgen -gen-register-info <input-file> -o <output-file> |
生成寄存器信息所对应的 C++ 代码 | llvm-tblgen -gen-register-info X86.td -I ../../../include |
llvm-tblgen -print-enums -class=<class-name> <input-file> -o <output-file> |
打印指定类别所对应的 C++ 代码中的枚举值。<class-name> 的可取值:Register 、Instruction 等。 |
llvm-tblgen -print-enums -class=Register X86.td -I ../../../include |
注:
如果不通过-o
选项显式地指定输出文件,那么会默认输出到控制台中。
-I
选项用于指定搜索路径。
TableGen
语言的词法和语法都是基于BNF
表示法进行描述的。常用的标点符号及意义如下:
标点符号 | 作用 | 示例 |
---|---|---|
| |
表示从多个模式中选择其中一个 | "+" | "-" ,表示选择"+" 或者"-" |
[] |
表示可以省略 | ["+" | "-"] ,表示可以选择"+" 或者"-" ,也可以什么都不选 |
... |
表示指定范围内的任意一个 | "0"..."9" ,表示 0 到 9 的数字 |
* |
表示重复 0 次或多次(需要用小括号括起来) | ("0"..."9")* ,表示 0 到 9 的数字可以重复 0 次或多次 |
+ |
表示重复 1 次或多次(需要用小括号括起来) | ("0"..."9")+ ,表示 0 到 9 的数字可以重复 1 次或多次 |
注:相关的官方文档——1.3 Lexical Analysis。
数值的词法:
TokInteger ::= DecimalInteger | HexInteger | BinInteger
DecimalInteger ::= ["+" | "-"] ("0"..."9")+
HexInteger ::= "0x" ("0"..."9" | "a"..."f" | "A"..."F")+
BinInteger ::= "0b" ("0" | "1")+
上述词法规则的含义:
TokInteger
,表示整数。它可以是十进制整数、十六进制整数或二进制整数。
DecimalInteger
,表示十进制整数。其识别规则为:以可以省略的+
或-
开头,后面是 1 个或多个0 到 9 的数字
。
HexInteger
,表示十六进制整数。其识别规则为:以0x
开头,后面是 1 个或多个0 到 9 的数字或者字母
。
BinInteger
,表示二进制整数。其识别规则为:以0b
开头,后面是 1 个或多个0 或 1 的数字
。
注:相关的官方文档——1.3.1 Literals。
字符串字面量的词法:
TokString ::= '"' (non-'"' characters and escapes) '"'
TokCode ::= "[{" (shortest text not containing "}]") "}]"
上述词法规则的含义:
TokString
,表示单行的字符串字面量。其识别规则为:以"
开头和结尾,中间可以是非"
字符和转义字符。
TokCode
,表示跨行的字符串字面量。其识别规则为:以[{
开头、以}]
结尾,中间可以是非}]
的字符串。
注:相关的官方文档——1.3.1 Literals。
标识符的词法:
ualpha ::= "a"..."z" | "A"..."Z" | "_"
TokIdentifier ::= ("0"..."9")* ualpha (ualpha | "0"..."9")*
TokVarName ::= "$" ualpha (ualpha | "0"..."9")*
上述词法规则的含义:
ualpha
,表示字母或下划线。
TokIdentifier
,表示标识符。其识别规则为:以 0 个或多个0 到 9 的数字
开头,中间是一个字母或者下划线,后面是 0 个或多个字母、下划线或者数字
。
TokVarName
,表示别名。其识别规则为:以$
开头,中间是一个字母或者下划线,后面是 0 个或多个字母、下划线或者数字
。
TableGen
语言中的保留字如下(不能作为标识符):
assert bit bits class code
dag def else false foreach
defm defset defvar field(已废弃) if
in include int let list
multiclass string then true
注:相关的官方文档——1.3.2 Identifiers。
数据类型的词法:
Type ::= "bit" | "int" | "string" | "dag"
| "bits" "<" TokInteger ">"
| "list" "<" Type ">"
| ClassID
ClassID ::= TokIdentifier
上述词法规则的含义:
bit
,表示布尔类型,可取值为 0 或 1。
int
,表示 64 位整数。比如:1、-2。
string
,表示任意长度的字符串。
dag
,表示可嵌套的有向无环图(Directed Acyclic Graph, DAG)。DAG 的节点由一个运算符、零个或多个参数(或操作数)组成。节点中的运算符必须是一个实例化的记录。DAG 的大小等于参数的数量。
bits<n>
,表示大小为n
的比特数组(数组中的每个元素占用一个比特)。注意: 比特数组中索引为 0 的元素位于最右侧。比如:bits<2> val = { 1, 0 },表示十进制整数 2。
list<type>
,表示元素数据类型为type
的列表。列表元素的下标从 0 开始。
ClassID
,表示抽象记录的名称。
注:相关的官方文档——1.4 Types、1.7.1 Directed acyclic graphs (DAGs)。
TableGen
语言支持两种注释方式:行注释// ...
和块注释/* ... */
。
每个TableGen
源文件对应一个不同的翻译单元。其语法如下:
TableGenFile ::= Statement*
从上面的语法可以看出,每个翻译单元是由 0 个或多个Statement
语句构成的。
注:相关的官方文档——1.6 Statements
语句Statement
的语法如下:
Statement ::= Assert | Class | Def | Defm | Defset | Defvar
| Foreach | If | Let | MultiClass
从上面的语法可以看出,TableGen
语言有Assert
、Class
、Def
等 10 种语句。
注:相关的官方文档——1.6 Statements
语句Class
用于定义一个抽象的记录类,它可以继承自一个或多个其他抽象记录类。其语法如下:
Class ::= "class" ClassID [TemplateArgList] RecordBody
TemplateArgList ::= "<" TemplateArgDecl ("," TemplateArgDecl)* ">"
TemplateArgDecl ::= Type TokIdentifier ["=" Value]
从上面的语法可以看出:
语句Class
是由保留字class
、抽象记录类名、可以省略的模板参数列表以及记录体构成的。
模板参数列表TemplateArgList
是由用<>
括起来的 1 个或多个模板参数构成的。其中,模板参数之间以,
分隔。
模板参数TemplateArgDecl
是由参数类型、参数名以及可以省略的默认值构成的。注意: 带默认值的模板参数只能出现在所有不带默认值的模板参数的后面。
示例(定义在 llvm/lib/Target/X86/X86InstrInfo.td 文件中):
class X86MemOperand<string printMethod,
AsmOperandClass parserMatchClass = X86MemAsmOperand> : Operand<iPTR> {
省略 ...
}
上述语句定义了一个继承自Operand
的抽象记录类X86MemOperand
,并且将值iPTR
传递给基类。该类的模板参数有两个,分别是:数据类型为string
的参数printMethod
(没有默认值),数据类型为AsmOperandClass
的参数parserMatchClass
(默认值为X86MemAsmOperand
)。
注:相关的官方文档——1.6.1 class — define an abstract record class、1.6.1.1 Record Bodies、1.4 Types。
非终端符RecordBody
表示记录体。其语法如下:
RecordBody ::= ParentClassList Body
ParentClassList ::= [":" ParentClassListNE]
ParentClassListNE ::= ClassRef ("," ClassRef)*
ClassRef ::= (ClassID | MultiClassID) ["<" [ValueList] ">"]
Body ::= ";" | "{" BodyItem* "}"
BodyItem ::= (Type | "code") TokIdentifier ["=" Value] ";"
| "let" TokIdentifier ["{" RangeList "}"] "=" Value ";"
| "defvar" TokIdentifier "=" Value ";"
| Assert
注:相关的官方文档——1.6.1.1 Record Bodies。
语句Def
用于定义一个具象记录(即抽象记录类的实例)。其语法如下:
Def ::= "def" [NameValue] RecordBody
NameValue ::= Value (parsed in a special mode)
从上面的语法可以看出:
语句Def
是由保留字def
、可以省略的具象记录名称以及记录体构成的。
具象记录名称NameValue
实质上是非终端符Value
,但以特殊模式进行解析。
示例(定义在 llvm/lib/Target/X86/X86RegisterInfo.td 文件中):
def DL : X86Reg<"dl", 2>;
上述语句定义了一个继承自X86Reg
的具象记录DL
,并且将值"dl"
和2
传递给基类。
具象记录DL
直接和间接继承的抽象记录基类X86Reg
和Register
的定义如下:
class Register<string n, list<string> altNames = []> {
string Namespace = "";
string AsmName = n;
list<string> AltNames = altNames;
list<Register> Aliases = [];
list<Register> SubRegs = [];
list<SubRegIndex> SubRegIndices = [];
list<RegAltNameIndex> RegAltNameIndices = [];
list<int> DwarfNumbers = [];
int CostPerUse = 0;
bit CoveredBySubRegs = false;
bits<16> HWEncoding = 0;
bit isArtificial = false;
}
class X86Reg<string n, bits<16> Enc, list<Register> subregs = []> : Register<n> {
let Namespace = "X86";
let HWEncoding = Enc;
let SubRegs = subregs;
}
具象记录DL
的实际内容如下:
def DL { // Register X86Reg
string Namespace = "X86";
string AsmName = "dl";
list<string> AltNames = [];
list<Register> Aliases = [];
list<Register> SubRegs = [];
list<SubRegIndex> SubRegIndices = [];
list<RegAltNameIndex> RegAltNameIndices = [];
list<int> DwarfNumbers = [];
int CostPerUse = 0;
bit CoveredBySubRegs = 0;
bits<16> HWEncoding = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 };
bit isArtificial = 0;
}
从上面的结果可以看出,具象记录DL
中包含了所有抽象记录基类中的字段。
需要注意的是, 如果具象记录的两个或多个抽象记录基类中存在相同的字段,那么具象记录实际使用的是最后一个抽象记录基类中的字段。
注:相关的官方文档——1.6.2 def — define a concrete record。
语句Multiclass
用于定义一个允许一次性实例化多个抽象记录类的组。其语法如下:
MultiClass ::= "multiclass" TokIdentifier [TemplateArgList]
[":" ParentMultiClassList]
"{" MultiClassStatement+ "}"
ParentMultiClassList ::= MultiClassID ("," MultiClassID)*
MultiClassID ::= TokIdentifier
MultiClassStatement ::= Assert | Def | Defm | Defvar | Foreach | If | Let
从上面的语法可以看出:
语句Multiclass
是由保留字multiclass
、组名、可以省略的模板参数列表、可以省略的基类列表以及用{}
括起来的 1 个或多个MultiClassStatement
语句构成的。
基类列表ParentMultiClassList
是由 1 个或多个其它组的名称构成的。注意: 不允许继承class
所定义的抽象记录类 。
语句MultiClassStatement
可以是Assert
、Def
或Defm
等语句,但不包括Class
、Defset
、MultiClass
这三种语句。
示例:
class I <bits<4> op> {
bits<4> opcode = op;
}
multiclass R {
def rr : I<4>;
def rm : I<2>;
}
上述示例的实际内容如下:
------------- Classes -----------------
class I<bits<4> I:op = { ?, ?, ?, ? }> {
bits<4> opcode = { I:op{3}, I:op{2}, I:op{1}, I:op{0} };
}
------------- Defs -----------------
从上面的结果可以看出,multiclass
本身不会实例化任何抽象记录类。
注:相关的官方文档——1.6.5 multiclass — define multiple records。
语句Defm
用于一次性实例化多个抽象记录类,其基类是 1 个或多个multiclass
所定义的组。其语法如下:
Defm ::= "defm" [NameValue] ParentClassList ";"
从上面的语法可以看出,语句Defm
是由保留字defm
、可以省略的具象记录名称、基类列表以及;
构成的。
示例:
class I <bits<4> op> {
bits<4> opcode = op;
}
multiclass R {
def rr : I<4>;
def rm : I<2>;
}
defm Instr : R;
上述示例的实际内容如下:
------------- Classes -----------------
class I<bits<4> I:op = { ?, ?, ?, ? }> {
bits<4> opcode = { I:op{3}, I:op{2}, I:op{1}, I:op{0} };
}
------------- Defs -----------------
def Instrrm { // I
bits<4> opcode = { 0, 0, 1, 0 };
}
def Instrrr { // I
bits<4> opcode = { 0, 1, 0, 0 };
}
从上面的结果可以看出,语句defm Instr : R;
生成了两个具象记录Instrrm
和Instrrr
。
注:相关的官方文档——1.6.6 defm—invoke multiclasses to define multiple records、1.6.7 Examples: multiclasses and defms。
语句Let
用于覆盖抽象记录类或具象记录中的字段。其语法如下:
Let ::= "let" LetList "in" "{" Statement* "}"
| "let" LetList "in" Statement
LetList ::= LetItem ("," LetItem)*
LetItem ::= TokIdentifier ["<" RangeList ">"] "=" Value
示例(定义在 llvm/lib/Target/X86/X86RegisterInfo.td 文件中):
let SubRegIndices = [sub_8bit, sub_8bit_hi], CoveredBySubRegs = 1 in {
def AX : X86Reg<"ax", 0, [AL,AH]>;
def DX : X86Reg<"dx", 2, [DL,DH]>;
def CX : X86Reg<"cx", 1, [CL,CH]>;
def BX : X86Reg<"bx", 3, [BL,BH]>;
}
上述示例的实际内容如下(部分):
def AX { // Register X86Reg
省略 ...
string AsmName = "ax";
省略 ...
list<Register> SubRegs = [AL, AH];
list<SubRegIndex> SubRegIndices = [sub_8bit, sub_8bit_hi];
省略 ...
bit CoveredBySubRegs = 1;
bits<16> HWEncoding = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
省略 ...
}
def DX { // Register X86Reg
省略 ...
string AsmName = "dx";
省略 ...
list<Register> SubRegs = [DL, DH];
list<SubRegIndex> SubRegIndices = [sub_8bit, sub_8bit_hi];
省略 ...
bit CoveredBySubRegs = 1;
bits<16> HWEncoding = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 };
省略 ...
}
def CX { // Register X86Reg
省略 ...
string AsmName = "cx";
省略 ...
list<Register> SubRegs = [CL, CH];
list<SubRegIndex> SubRegIndices = [sub_8bit, sub_8bit_hi];
省略 ...
bit CoveredBySubRegs = 1;
bits<16> HWEncoding = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
省略 ...
}
def BX { // Register X86Reg
省略 ...
string AsmName = "bx";
省略 ...
list<Register> SubRegs = [BL, BH];
list<SubRegIndex> SubRegIndices = [sub_8bit, sub_8bit_hi];
省略 ...
bit CoveredBySubRegs = 1;
bits<16> HWEncoding = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 };
省略 ...
}
从上面的结果可以看出,上述示例中的Let
语句将具象记录AX
、DX
、CX
和BX
中的字段SubRegIndices
的值设置为[sub_8bit, sub_8bit_hi]
、字段CoveredBySubRegs
的值设置为1
。
注:相关的官方文档——1.6.4 let—override fields in classes or records。
下一篇:LLVM 之后端篇(2):如何扩展 llc 的目标后端
上一篇:上一级目录