LLVM TableGen

Table of Contents

1. LLVM TableGen

https://llvm.org/docs/TableGen/index.html

https://llvm.org/docs/TableGen/ProgRef.html

tablegen 是整个 llvm backend 核心的数据结构, llvm backend 的所有组件都需要通过 tablegen 来解释 target description (td) 文件, 拿到自己关注的数据.

要了解 td 文件, 除了 tablegen 本身的语法, 更重要的是 SelectionDAGISel 和其它模块如何使用 tablegen 的数据, 因为这些数据完全是由各个模块负责解释.

target description 和 gcc machine description 基本是类似, 它们都包含:

  1. instruction selection 相关的 pattern
  2. target instruction 对应的 assembly
  3. 和 scheduling 相关的 instruction latency

但 td 是使用一个新的 DSL 对这些信息进行层次化的定义, 而 md 的定义并不是层次化的: 例如 add,sub 指令需要有单独的完整定义, 虽然它们有许多相似的地方.

另外, md 不包含寄存器信息和指令的编码信息, 所以 gcc 需要自己从头实现 as/disass, 而不是像 llvm 可以使用 td 的信息自动生成 as/disass 的部分代码.

1.1. llvm-tblgen

llvm-tblgen 包括两部分功能:

  1. 解析 td 文件, 生成 records, 这个 records 是 td 展开的结果, 不涉及具体的语义
  2. 把 records 按命令行参数提供的 action 进一步解析, 生成对应的 inc 文件, 例如 `-gen-dag-isel` 参数会导致 llvm-tblgen 按 isel 的需求去解析 records, 生成 RISCVGenDAGISel.inc

所以 llvm-tblgen 是认识 llvm 相关的 action 的语义的, 例如 `gen-register-info`, `gen-instr-info`, `gen-dag-isel`…, 但通过 `-print-records` 和 `-print-detailed-records`, 可以只看 td 展开的 records, 而不做进一步分析.

1.2. td

  1. TableGen DSL 类似于 c++
  2. 支持类似模板的参数
  3. 支持多重继承
  4. 通过 class 关键字定义 abstract record
  5. 通过 def 关键字来定义 concrete record

使用 `llvm-tblgen xxx.td [-print-records, -print-detailed-records]` 可以查找 td 展开后的结果.

1.2.1. example

// 使用 class 来定义一个 abstract record
class A<string n> {
      string Name=n;
}

class B<int a> {
      // 定义了四个 field
      // 支持 string, int, bit, bits<>, list 五种数据类型
      int A=a;
      bit Bit;
      bits<10> Bits;
      list<int> List=[a];
      // 其它 class 的实例也能做为 field
      A ClassA;
}

// 多重继承, 并通过模板参数初始化基类
class AB<string n,int a, A x>:A<n>,B<a> {
      // bits 可以通过 {a-b} 的形式访问 bit
      // 通过 let 修改基类的 field
      let Bits{2-0}=a;
      let Bit=true;
      let List=[a,a];
      // 声明 AB 类自己的 field, 通过 Name 访问基类的 field
      // # 用来拼接
      string X=Name#Name;
      let ClassA=x;
}

// 使用 def 定义 concrete record
def a:A<"a">{
}

def ab0:AB<"0",2, a> {
    // def 可以使用通过 let 修改 class field
    let Bits{4-2}=2;
    // def 可以定义自己的 field
    int AA=1;
}

// let 可以放在 class, def 外面, 通过 let xxx in {} 的形式给多个 record 的 field
// 赋值, 这个和 lisp 的 (let ((a 1)) (xxx)) 很类似
let Name="ab12",Bit=false in {
    def ab1:AB<"0",2,a>;
    def ab2:AB<"0",2,a>;
}

// let 可以嵌套
let Name="abc" in {
    let Name="bcd" in {
        class ABC:A<"">;
    }
}

// multiclass/defm 的作用和 gcc md 的 mode iterator 类似
multiclass ALU {
    def rr {
        string type="RR";
    }
    def ri {
        string type="RI";
    }
}

defm add:ALU;
defm sub:ALU;

// foreach, 相当于带循环的 let
foreach i=[1,2,3] in {
     def a_#i:A<"">{}
}

// bang operators, 即预定义的 !xxx 形式的函数

def aaa {
    string x=!cast<string>(1);
    int y=!add(1,2);
    int z=!cond(!lt(y,2):0,true:1);
    int a=!if(!eq(y,3),0,1);
}

llvm-tblgen test.td 展开的结果:

------------- Classes -----------------
class A<string A:n = ?> {
  string Name = A:n;
}
class AB<string AB:n = ?, int AB:a = ?, A AB:x = ?> {	// A B
  string Name = AB:n;
  int A = AB:a;
  bit Bit = 1;
  bits<10> Bits = { ?, ?, ?, ?, ?, ?, ?, !cast<bits<3>>(AB:a){2}, !cast<bits<3>>(AB:a){1}, !cast<bits<3>>(AB:a){0} };
  list<int> List = [AB:a, AB:a];
  A ClassA = AB:x;
  string X = !strconcat(Name, Name);
}
class ABC {	// A
  string Name = "bcd";
}
class B<int B:a = ?> {
  int A = B:a;
  bit Bit = ?;
  bits<10> Bits = { ?, ?, ?, ?, ?, ?, ?, ?, ?, ? };
  list<int> List = [B:a];
  A ClassA = ?;
}
------------- Defs -----------------
def a {	// A
  string Name = "a";
}
def a_1 {	// A
  string Name = "";
}
def a_2 {	// A
  string Name = "";
}
def a_3 {	// A
  string Name = "";
}
def aaa {
  string x = "1";
  int y = 3;
  int z = 1;
  int a = 0;
}
def ab0 {	// A B AB
  string Name = "0";
  int A = 2;
  bit Bit = 1;
  bits<10> Bits = { ?, ?, ?, ?, ?, 0, 1, 0, 1, 0 };
  list<int> List = [2, 2];
  A ClassA = a;
  string X = "00";
  int AA = 1;
}
def ab1 {	// A B AB
  string Name = "ab12";
  int A = 2;
  bit Bit = 0;
  bits<10> Bits = { ?, ?, ?, ?, ?, ?, ?, 0, 1, 0 };
  list<int> List = [2, 2];
  A ClassA = a;
  string X = "ab12ab12";
}
def ab2 {	// A B AB
  string Name = "ab12";
  int A = 2;
  bit Bit = 0;
  bits<10> Bits = { ?, ?, ?, ?, ?, ?, ?, 0, 1, 0 };
  list<int> List = [2, 2];
  A ClassA = a;
  string X = "ab12ab12";
}
def addri {
  string type = "RI";
}
def addrr {
  string type = "RR";
}
def subri {
  string type = "RI";
}
def subrr {
  string type = "RR";
}

1.3. gen-dag-isel

和 isel 相关的 class 主要有:

  1. Instruction, 其中包含 Pattern, Predicates, Constraints, SchedRW, 在 Target.td 中
  2. Pattern, 包含 PatternToMatch, Predicates, 在 TargetSelectionDAG.td 中

以 FADD_S 为例:

// 通过 SchedRW 定义了 FADD_S Instruction 的 SchedReadWrite,
// 这个相当于 gcc md 中的 (set-attr "type" ...).
// 后续相应的 SchedMachineModel 会定义 WriteFALU32 在 machine (例如 sifive rocket)
// 上的 Latency, ResourceCycles 和 ProcResourceKind
// RISCVSchedRocket.td:
// let Latency = 4 in {
//     def : WriteRes<WriteFALU32, [RocketUnitFPALU]>;
// }
// 导致 Instruction 的 Latency 为 4
let SchedRW = [WriteFALU32, ReadFALU32, ReadFALU32] in {
    defm FADD_S : FPALU_rr_frm_m<0b0000000, "fadd.s", FINX>;
    defm FSUB_S : FPALU_rr_frm_m<0b0000100, "fsub.s", FINX>;
}

// FINX 是一个 ext list, 展开后最终能看到 Instruction 会包含一个
// hasExtF 的 predicate, 但并不包含 Pattern

def : PatFprFprDynFrm<any_fadd, FADD_S, FPR32>;

// PatFprFprDynFrm 展开为:
// Pat<(fadd FPR32:$rs1, FPR32:$rs2), (F_ADD_S $rs1, $rs2, 0b111)>;
// 后者再展开成一个 Pattern, 其中 (fadd FPR32:$rs1, FPR32:$rs2) 是 PatternToMatch
// (F_ADD_S $rs1, $rs2, 0b111) 是 ResultInstrs, 没有额外的 Predicates

把 RISCV.td 完整整开后关于 FADD_S 的部分为:

$> build/bin/llvm-tblgen -I llvm/lib/Target/RISCV -Ibuild/include \
   -I/home/sunway/source/llvm-project/llvm/include -I llvm/lib/Target llvm/lib/Target/RISCV/RISCV.td

def FADD_S {	// InstructionEncoding Instruction RVInst RVInstRFrm FPALU_rr_frm
  ...
  list<Predicate> Predicates = [HasStdExtF];
  string AsmString = "fadd.s	$rd, $rs1, $rs2, $frm";
  list<dag> Pattern = [];
  int AddedComplexity = 0;
  list<SchedReadWrite> SchedRW = [WriteFALU32, ReadFALU32, ReadFALU32];
  RISCVVConstraint RVVConstraint = NoConstraint;
  ...
}

def anonymous_5505 {	// Pattern Pat PatFprFprDynFrm
  dag PatternToMatch = (any_fadd FPR32:$rs1, FPR32:$rs2);
  list<dag> ResultInstrs = [(FADD_S ?:$rs1, ?:$rs2, { 1, 1, 1 })];
  list<Predicate> Predicates = [HasStdExtF];
  int AddedComplexity = 0;
}

def anonymous_45523 {	// ProcWriteResources WriteRes
  list<ProcResourceKind> ProcResources = [RocketUnitFPALU];
  list<int> ResourceCycles = [];
  int Latency = 4;
  int NumMicroOps = 1;
  bit BeginGroup = 0;
  bit EndGroup = 0;
  bit Unsupported = 0;
  bit SingleIssue = 0;
  bit RetireOOO = 0;
  SchedMachineModel SchedModel = RocketModel;
  SchedWrite WriteType = WriteFALU32;
}

def anonymous_45659 {	// ProcWriteResources WriteRes
  list<ProcResourceKind> ProcResources = [SiFive7PipeB];
  list<int> ResourceCycles = [];
  int Latency = 5;
  int NumMicroOps = 1;
  bit BeginGroup = 0;
  bit EndGroup = 0;
  bit Unsupported = 0;
  bit SingleIssue = 0;
  bit RetireOOO = 0;
  SchedMachineModel SchedModel = SiFive7Model;
  SchedWrite WriteType = WriteFALU32;
}

1.4. gen-instr-info

Backlinks

LLVM Backend (LLVM Backend > TableGen): TableGen

Author: [email protected]
Date: 2022-05-11 Wed 14:53
Last updated: 2024-02-01 Thu 14:04

知识共享许可协议