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 概述

TableGen是一种由 LLVM 团队开发的声明式编程语言,用于描述编译器多个阶段中所使用的信息。比如:描述不同的 Clang 诊断、描述不同的静态分析检查器等。通过不同的TableGen处理工具,这些信息可以进一步转换成对 Clang 诊断、Clang 驱动器、Clang 静态分析器、代码生成器等有实际含义的特定信息。

TableGen可用于描述 LLVM 后端中各种特定于目标的信息,比如:指令格式、指令、寄存器、模式匹配有向无环图、指令选择匹配顺序、调用约定和目标 CPU 属性等。从而,易于维护 LLVM 后端的代码。

TableGen源文件是 ASCII 文本文件,习惯上以.td结尾。


TableGen 的相关命令

在 LLVM 12.0.0 版本中,TableGen处理工具包括以下几种:clang-tblgenlldb-tblgenllvm-tblgenmlir-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>的可取值:RegisterInstruction等。 llvm-tblgen -print-enums -class=Register X86.td -I ../../../include

注:


TableGen 的词法


标点符号

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")+

上述词法规则的含义:

注:相关的官方文档——1.3.1 Literals

返回上一级


字符串字面量

字符串字面量的词法:

TokString ::=  '"' (non-'"' characters and escapes) '"'
TokCode   ::=  "[{" (shortest text not containing "}]""}]"

上述词法规则的含义:

注:相关的官方文档——1.3.1 Literals

返回上一级


标识符

标识符的词法:

ualpha        ::=  "a"..."z" | "A"..."Z" | "_"
TokIdentifier ::=  ("0"..."9")* ualpha (ualpha | "0"..."9")*
TokVarName    ::=  "$" ualpha (ualpha |  "0"..."9")*

上述词法规则的含义:

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

上述词法规则的含义:

注:相关的官方文档——1.4 Types1.7.1 Directed acyclic graphs (DAGs)

返回上一级


注释

TableGen语言支持两种注释方式:行注释// ...和块注释/* ... */

返回上一级


TableGen 的语法


Translation Unit

每个TableGen源文件对应一个不同的翻译单元。其语法如下:

TableGenFile ::=  Statement*

从上面的语法可以看出,每个翻译单元是由 0 个或多个Statement语句构成的。

注:相关的官方文档——1.6 Statements

返回上一级


Statement

语句Statement的语法如下:

Statement    ::=  Assert | Class | Def | Defm | Defset | Defvar
                 |
 Foreach | If | Let | MultiClass

从上面的语法可以看出,TableGen语言有AssertClassDef等 10 种语句。

注:相关的官方文档——1.6 Statements

返回上一级


Class

语句Class用于定义一个抽象的记录类,它可以继承自一个或多个其他抽象记录类。其语法如下:

Class           ::=  "class" ClassID [TemplateArgList] RecordBody
TemplateArgList ::=  "<" TemplateArgDecl ("," TemplateArgDecl)* ">"
TemplateArgDecl ::=  Type TokIdentifier ["=" Value]

从上面的语法可以看出:

示例(定义在 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 class1.6.1.1 Record Bodies1.4 Types

返回上一级


RecordBody

非终端符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       ::=  "def" [NameValue] RecordBody
NameValue ::=  Value (parsed in a special mode)

从上面的语法可以看出:

示例(定义在 llvm/lib/Target/X86/X86RegisterInfo.td 文件中):

def DL : X86Reg<"dl"2>;

上述语句定义了一个继承自X86Reg的具象记录DL,并且将值"dl"2传递给基类。

具象记录DL直接和间接继承的抽象记录基类X86RegRegister的定义如下:

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 = { 0000000000000010 };
  bit isArtificial = 0;
}

从上面的结果可以看出,具象记录DL中包含了所有抽象记录基类中的字段。

需要注意的是, 如果具象记录的两个或多个抽象记录基类中存在相同的字段,那么具象记录实际使用的是最后一个抽象记录基类中的字段。

注:相关的官方文档——1.6.2 def — define a concrete record

返回上一级


Multiclass

语句Multiclass用于定义一个允许一次性实例化多个抽象记录类的组。其语法如下:

MultiClass           ::=  "multiclass" TokIdentifier [TemplateArgList]
                          [":" ParentMultiClassList]
                          "{" MultiClassStatement+ "}"
ParentMultiClassList ::=  MultiClassID ("," MultiClassID)*
MultiClassID         ::=  TokIdentifier
MultiClassStatement  ::=  Assert | Def | Defm | Defvar | Foreach | If | Let

从上面的语法可以看出:

示例:

class I <bits<4> op> {
  bits<4> opcode = op;
}

multiclass R {
  def rr : I<4>;
  def rm : I<2>;
}

上述示例的实际内容如下:

------------- Classes -----------------
class I<bits<4I: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

语句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<4I:op = { ???? }> {
  bits<4> opcode = { I:op{3}, I:op{2}, I:op{1}, I:op{0} };
}
------------- Defs -----------------
def Instrrm {    // I
  bits<4> opcode = { 0010 };
}
def Instrrr {    // I
  bits<4> opcode = { 0100 };
}

从上面的结果可以看出,语句defm Instr : R;生成了两个具象记录InstrrmInstrrr

注:相关的官方文档——1.6.6 defm—invoke multiclasses to define multiple records1.6.7 Examples: multiclasses and defms

返回上一级


Let

语句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 = { 0000000000000000 };
  省略 ...
}

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 = { 0000000000000010 };
  省略 ...
}

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 = { 0000000000000001 };
  省略 ...
}

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 = { 0000000000000011 };
  省略 ...
}

从上面的结果可以看出,上述示例中的Let语句将具象记录AXDXCXBX中的字段SubRegIndices的值设置为[sub_8bit, sub_8bit_hi]、字段CoveredBySubRegs的值设置为1

注:相关的官方文档——1.6.4 let—override fields in classes or records

返回上一级


References


下一篇:LLVM 之后端篇(2):如何扩展 llc 的目标后端

上一篇:上一级目录

首页