有关于TableGen语言语法的文章,LLVM官方发布有两篇,第一篇是:TableGen Language Introduction,第二篇是:TableGen Language Reference(version llvm 10.0.0)。文章开头声明说,第一篇不是规范的参考文档,第二篇是规范的参考文档,并且两篇都有点年久失修。我把两篇都看了一下,确实感觉第二篇更规范一些,尤其是语法描述的章节,特别严谨。但是,我这里还是选择以第一篇的内容作为参考文档,主要是因为从易读性的角度来说,第一篇更容易理解(读者友好型),当然,第二篇作为参考查阅更便捷(活学活用Ctrl-f),关键在于能通过学习,掌握TableGen语言语法。
需要说明:本文中提到的
记录
是指官方文章中的record
,即通过def
或defm
等语法定义的实例化的数据对象。
文章目录
TableGen语法的特点
在我看来,TableGen的语法使用起来特别的灵活,但它还确实是一个强类型的语法,由于TableGen的作用只是用来描述信息,所以几乎没有控制流的语法规范,它也不关心语言本身的意义是否正确(由TableGen后端关心),它只关心语言本身的语法是否合法。它具有一些不同数据类型的定义,也支持一定程度的类型转换,数据类型范围特别宽泛。
以下介绍一下主要的语法。
TableGen语法介绍
注释
使用C++的注释风格://
,不过也支持C风格的嵌套注释:/* */
。
数据类型
由于它是一个强类型语言,所以我们需要在编写td文件时考虑到数据格式规范。另外,它的覆盖范围特别广,小到位类型,大到dag类型,还支持类参数类型的扩展和列表的扩展,这使得TableGen非常的灵活和易于使用。
主要类型有:
bit
:一位就是表示一个布尔值,比如0或者1;int
:表示一个32位的整形值,比如5;string
:表示一个有序的固定长度的字符序列,比如“add”;code
:表示一个代码片段,可以是单行或多行,不过其实和string
本质一样,只是展示的意义不同而已;bits<n>
:bit
的扩展,可以指定n个位同时赋值,比如当n=3,可以是010,指定位模式时特别常用;list<ty>
:很灵活的一个类型,可以保存指定ty类型的数据的列表,ty类型也可以是另一个list<ty>
,可以理解是C++中的List模板类;class
:指定一些类型数据的集合表示,必须用def或defm来使用这个类定义记录之后,内部数据才被分配。用来声明多个记录的共有信息,支持继承和重载等特性;dag
:表示可嵌套的有向无环图元素;
原文中指出:当前这些数据类型已经足够使用,如果日后还添加其他数据类型,再另行发布(我也不知道会不会更新这篇文章,以官方为准)
值和表达式
也非常灵活,有些时候,def的参数列表搞的非常长,就是因为这一部分,阅读代码会比较累,也没办法。
-
?:未定义;
-
0b1001001:位值,注意它的长度是固定的,它不会自动扩展或截断;
-
7:十进制数;
-
0x7F:十六进制数;
-
“foo”:单行的字符串值,可以直接赋给
string
或code
; -
[{ … }]:代码片段,通常用于赋给
code
,但其实就是多行字符串值; -
[ X, Y, Z ]<type>:列表,type是指定列表中元素类型,一般情况下可省略,TableGen前端能够推测类型,极少数特殊情况下需要明确指定;
-
{ a, b, 0b10 }:初始化
bits<n>
这样的类型,第一位是a变量的值,第二位是b变量的值,第三位和第四位是’0b1’ 和 ‘0b0’; -
value:值的引用,比如上边出现的
X
,Y
,Z
,a
,b
; -
value{17}:值的引用并截取一位;
-
value{15-17}:值的引用并截取一部分位,
-
两边必须要连续 -
DEF:记录的引用;
-
CLASS<val list>:匿名定义的引用,
<val list>
是模版参数,这里是这个意思,对于一个带模版参数的类,通过指定模版参数,可以直接定义一个匿名的记录,这里就是引用这个匿名记录; -
X.Y:引用一个值的子域,常用在记录上;
-
list[4-7,17,2-3]:列表片段,比如这个例子中,引用了列表list的第4、5、6、7、17、2、3这几位;
-
foreach <var> = [ <list> ] in { <body> }:一种循环结构,依次将list中的值赋给var,并执行body体,类似于C++11中的foreach,body中仅可以包含def和defm;
-
foreach <var> = [ <list> ] in <def>:同上,不同是循环体只有一条语句,不需要用
{}
; -
foreach <var> = 0-15 in …:同上,不同的是循环的是整数;
-
foreach <var> = {0-15,32-47} in …:同上,不同的是循环的是几个整数片段;
-
(DEF a, b, …):这是dag类型的表示,第一个参数
DEF
是一个记录的定义,剩下的参数可以是其他类型值,当然也包括嵌套的dag类型值,除第一个参数外,其他参数可省略; -
!con(a, b, …):将两个或多个dag类型节点连接,它们的操作码必须相同;
例子:
!con((op a1:$name1, a2:$name2), (op b1:$name3))
,等效于(op a1:$name1, a2:$name2, b1:$name3)
。其中 a1,a2,b1 均表示接后边值的类型。 -
!dag(op, children, names):生成一个dag节点,children和names必须有相同的长度的列表或者是
?
,names必须是list<string>
类型,children必须是常见类型的列表,children列表中的类型必须是相同的或者它们的祖先类是相同的,不支持混合的dag和non-dag;例子:
!dag(op, [a1, a2, ?], ["name1", "name2", "name3"])
,等效于(op a1:$name1, a2:$name2, ?:name3)
。 -
!listconcat(a, b, …):将两个或多个列表合并成一个列表,这些子列表必须具有相同的子项类型;
-
!listsplat(a, size):将指定a列表中的子项重复包含size次;
例子:
!listsplat(0, 2)
,等效于[0, 0]
。 -
!strconcat(a, b, …):将两个或多个字符串合并成一个字符串;
-
!str1#str2:将两个字符串合并成一个字符串,是
!strconcat(a, b)
的简化用法,如果其中有不是字符串的类型,TableGen前端会隐式调用!cast<string>
操作转换为字符串类型。 -
!cast<type>(a):强制类型转换。(注:这段我方了,没有完全理解,后边解释不用看,感兴趣的同学回去翻原文吧)
如果a是字符串,而type是记录类型,那么将会通过完全检查a和所有记录之间的匹配,并确保匹配记录已经完整声明。这个完整声明的意思是,如果这个记录是在含参模版类中,那么这个记录在定义时,必须要求该类和其内部可能有的其他含参类都已指明类参数值;而如果还没有指定完整的类参数值,那么会在指定完整类参数值之后再执行这次转换,而如果没有匹配到任何记录,则会报错。若type只是简单的值类型,比如bit或int之间,或记录之间。对于记录之间的情况,cast会首先将记录转换为子类,如果类型不匹配,则不会做常量折叠。!cast 是一个特殊的情况,他的参数 a 可以是一个 int 或记录类型,如果满足记录类型的转换,则会返回一个类型名; -
!isa<type>(a):返回布尔值,如果a是type类型,返回1,否则返回0;
-
!subst(a, b, c):如果a和b是字符串类型或者是引用类型,将c中的b替换为a,类似于GNU make中的
$(subst)
语法; -
!foreach(a, b, c):对于b中的每一个值,将c中的b替换为a,类似于GNU make中的
$(foreach)
语法(不是 C++ 中的那个 foreach); -
!foldl(start, lst, a, b, expr):使用给定的start,对lst做left-fold操作。a和b是变量名,将在expr中被替换,如果expr视为函数
f(a,b)
,则left-fold将做这样的操作:f((...f(f(start, lst[0]), lst[1]), ...), lst[n-1])
,循环次数取决于lst的长度n。a与start相同类型,b与lst中元素相同类型,expr和start相同类型; -
!head(a):取列表a的第一个元素;
-
!tail(a):取列表a的最后一个元素(原文中是:`The 2nd-N elements of list ‘a’,是同一个意思吗);
-
!empty(a):返回布尔值,列表a是否为空;
-
!size(a):返回一个整数,表示列表a的元素个数;
-
!if(a, b, c):类似C中的三元操作符:
? :
,如果a为非0,返回b,否则返回c; -
!cond(condition_1 : val1, condition_2: val2, …, condition_n : valn):是
!if(a, b, c)
的扩展,避免多次的if嵌套。如果condition_1满足,返回val1,否则如果condition_2满足,返回val2,以此类推,如果condition_n仍然不满足,返回错误;例子:
!cond(!lt(x, 0) : "negative", !eq(x, 0) : "zero", 1 : "positive")
,注意到最后一个条件是恒成立,避免了可能的报错。 -
!eq(a, b):返回布尔值,如果a和b相等,返回1,否则返回0,注意这个只对
string
、int
和bit
对象生效,其他类型可以尝试先做!cast<string>
操作; -
!ne(a, b):返回布尔值,如果a和b不相等,返回1,否则返回0,a、b取值类型同
!eq(a, b)
。 -
!le(a, b), !lt(a, b), !ge(a, b), !gt(a, b):返回布尔值,分别是小于等于(little equal),小于(little than),大于等于(great equal),大于(great than),成立返回1,否则返回0;
-
!shl(a, b), !srl(a, b), !sra(a, b):位移操作,分别是逻辑左移(shift left logical),逻辑右移(shift right logical),算数右移(shift right arithmetic)。操作64位整数,如果如果移位大于63或小于0,则结果是无效的;
-
!add(a, b, …), !mul(a, b, …), !and(a, b, …), !or(a, b, …):运算指令,加(add),乘(mul),与(and), 或(or)
类和定义
类和定义(也叫做记录&