TabaleGen介绍

本文翻译自TableGen Overview — LLVM 15.0.0git documentation

TableGen的目的是帮助开发和维护领域特定信息的记录。因为可能有大量这样的记录,所以专门设计它来允许编写灵活的描述,并提取出这些记录的共同特性。这减少了描述中的重复数量,减少了出错的机会,并使结构领域特定信息变得更容易。

TableGen前端解析文件,实例化声明,并将结果传递给特定领域的后端进行处理。有关TableGen的深入描述,请参阅TableGen Programmer’s Reference 参考。请参阅 tblgen - Description to C++ Code以了解运行各种版本的TableGen的*-tblgen命令的详细信息。

目前TableGen的主要使用者是 The LLVM Target-Independent Code Generator和 Clang diagnostics and attributes

注意,如果您经常使用TableGen,并且使用emacs或vim,您可以在llvm发行版的llvm/utils/emacs和llvm/utils/vim目录中分别找到emacs " TableGen模式"和vim语言文件。

TableGen程序

TableGen文件由TableGen程序解释:llvm-tblgen, 在bin下的build目录可以找到。它没有安装在系统中,因为它除了LLVM的构建过程之外没有其他用途。

运行TableGen 

TableGen就像其他LLVM工具一样运行。第一个(可选)参数指定要读取的文件。如果没有指定文件名,llvm-tblgen将从标准输入中读取。

为了有用,必须使用其中一个后端。这些后端可以从命令行中选择(输入' llvm-tblgen -help '查看列表)。例如,要获得一个特定类型子类化的所有定义的列表(这对于构建这些记录的枚举列表很有用),使用-print-enum选项: 

$ llvm-tblgen X86.td -print-enums -class=Register
AH, AL, AX, BH, BL, BP, BPL, BX, CH, CL, CX, DH, DI, DIL, DL, DX, EAX, EBP, EBX,
ECX, EDI, EDX, EFLAGS, EIP, ESI, ESP, FP0, FP1, FP2, FP3, FP4, FP5, FP6, IP,
MM0, MM1, MM2, MM3, MM4, MM5, MM6, MM7, R10, R10B, R10D, R10W, R11, R11B, R11D,
R11W, R12, R12B, R12D, R12W, R13, R13B, R13D, R13W, R14, R14B, R14D, R14W, R15,
R15B, R15D, R15W, R8, R8B, R8D, R8W, R9, R9B, R9D, R9W, RAX, RBP, RBX, RCX, RDI,
RDX, RIP, RSI, RSP, SI, SIL, SP, SPL, ST0, ST1, ST2, ST3, ST4, ST5, ST6, ST7,
XMM0, XMM1, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15, XMM2, XMM3, XMM4, XMM5,
XMM6, XMM7, XMM8, XMM9,

$ llvm-tblgen X86.td -print-enums -class=Instruction
ABS_F, ABS_Fp32, ABS_Fp64, ABS_Fp80, ADC32mi, ADC32mi8, ADC32mr, ADC32ri,
ADC32ri8, ADC32rm, ADC32rr, ADC64mi32, ADC64mi8, ADC64mr, ADC64ri32, ADC64ri8,
ADC64rm, ADC64rr, ADD16mi, ADD16mi8, ADD16mr, ADD16ri, ADD16ri8, ADD16rm,
ADD16rr, ADD32mi, ADD32mi8, ADD32mr, ADD32ri, ADD32ri8, ADD32rm, ADD32rr,
ADD64mi32, ADD64mi8, ADD64mr, ADD64ri32, ...

 默认后端打印出所有记录。还有一个通用后端,使能-dump-json选项情况下,它将所有记录输出为JSON数据结构。

如果您计划使用TableGen,那么很可能必须编写一个后端来提取您所需的特定信息,并以适当的方式对其进行格式化。为此,可以使用C++扩展TableGen,或者使用任何支持JSON输出的语言编写脚本。

示例

在没有其他参数的情况下,llvm-tblgen将解析指定的文件并输出所有类,然后输出所有定义。这是一个很好的方法,可以看到各种定义完全扩展到什么程度。在X86.td上运行会打印这些(在撰写本文的时候):

...
def ADD32rr {   // Instruction X86Inst I
  string Namespace = "X86";
  dag OutOperandList = (outs GR32:$dst);
  dag InOperandList = (ins GR32:$src1, GR32:$src2);
  string AsmString = "add{l}\t{$src2, $dst|$dst, $src2}";
  list<dag> Pattern = [(set GR32:$dst, (add GR32:$src1, GR32:$src2))];
  list<Register> Uses = [];
  list<Register> Defs = [EFLAGS];
  list<Predicate> Predicates = [];
  int CodeSize = 3;
  int AddedComplexity = 0;
  bit isReturn = 0;
  bit isBranch = 0;
  bit isIndirectBranch = 0;
  bit isBarrier = 0;
  bit isCall = 0;
  bit canFoldAsLoad = 0;
  bit mayLoad = 0;
  bit mayStore = 0;
  bit isImplicitDef = 0;
  bit isConvertibleToThreeAddress = 1;
  bit isCommutable = 1;
  bit isTerminator = 0;
  bit isReMaterializable = 0;
  bit isPredicable = 0;
  bit hasDelaySlot = 0;
  bit usesCustomInserter = 0;
  bit hasCtrlDep = 0;
  bit isNotDuplicable = 0;
  bit hasSideEffects = 0;
  InstrItinClass Itinerary = NoItinerary;
  string Constraints = "";
  string DisableEncoding = "";
  bits<8> Opcode = { 0, 0, 0, 0, 0, 0, 0, 1 };
  Format Form = MRMDestReg;
  bits<6> FormBits = { 0, 0, 0, 0, 1, 1 };
  ImmType ImmT = NoImm;
  bits<3> ImmTypeBits = { 0, 0, 0 };
  bit hasOpSizePrefix = 0;
  bit hasAdSizePrefix = 0;
  bits<4> Prefix = { 0, 0, 0, 0 };
  bit hasREX_WPrefix = 0;
  FPFormat FPForm = ?;
  bits<3> FPFormBits = { 0, 0, 0 };
}
...

这个定义对应于x86架构的32位寄存器-寄存器add指令。def ADD32rr定义了一条名为ADD32rr的记录,该行末尾的注释指出了该定义的超类。记录的主体包含了TableGen为该记录组装的所有数据。从这些数据可以看到,该指令是“X86”命名空间的一部分;指令被代码生成器选择的模式:它是一个双地址指令,具有特定的编码,等等。记录中信息的内容和语义是针对X86后端的,这里仅作为示例。

正如您所看到的,代码生成器支持的每条指令都需要大量的信息,并且手动指定所有这些信息将是不可维护的,容易产生bug,而且一开始就很累人。因为我们使用的是TableGen,所以所有的信息都来自以下定义:

let Defs = [EFLAGS],
    isCommutable = 1,                  // X = ADD Y,Z --> X = ADD Z,Y
    isConvertibleToThreeAddress = 1 in // Can transform into LEA.
def ADD32rr  : I<0x01, MRMDestReg, (outs GR32:$dst),
                                   (ins GR32:$src1, GR32:$src2),
                 "add{l}\t{$src2, $dst|$dst, $src2}",
                 [(set GR32:$dst, (add GR32:$src1, GR32:$src2))]>;

这个定义利用在x86独有的TableGen文件中定义的自定义类I(从自定义类X86Inst扩展而来),提取出该类指令共享的公共特性。TableGen的一个关键特性是,它允许终端用户定义抽象,这是用户在描述信息时喜欢的方式。

语法

TableGen有一种基于c++模板但是更松散的语法,具有内置的类型和规范。此外,TableGen的语法引入了一些自动化概念,如multiclass、foreach、let等。

基本概念 

TableGen文件由两个关键部分组成:' classes '和' definitions ',这两个部分都被认为是' records '。

TableGen中记录有一个惟一的名称、一个值列表和一个超类列表。值列表是TableGen为每条记录构建的主要数据;它保存了应用程序的特定于域的信息。这个数据的解释留给特定的后端,但是结构和格式规则由TableGen负责并固定。

TableGen中定义是“records”的具体形式。它们通常没有任何未定义的值,并使用' def '关键字进行标记。

def FeatureFPARMv8 : SubtargetFeature<"fp-armv8", "HasFPARMv8", "true",
                                      "Enable ARMv8 FP">;

 在这个例子中,FeatureFPARMv8是SubtargetFeature记录用一些值初始化。类的名称通过关键字class在同一文件或包含的其他文件中定义。大多数目标TableGen文件包含include/llvm/Target目录下的泛型定义。

TableGen中是用于构建和描述其他记录的抽象记录。这些类允许最终用户为他们的目标域(如“Register”,“RegisterClass”,和LLVM代码生成器中的“Instruction”)或实现者构建抽象,以帮助提取出记录的公共属性(如“FPInst”,它用于表示X86后端中的浮点指令)。TableGen跟踪用于构建定义的所有类,因此后端可以找到特定类的所有定义,如“Instruction”。 

class ProcNoItin<string Name, list<SubtargetFeature> Features>
      : Processor<Name, NoItineraries, Features>;

在这里,类ProcNoItin,接收类型为string的Name和目标特性列表,通过向下传递参数以及硬编码NoItineraries来专门化类Processor。

TableGen多类是一组一次性实例化的抽象记录。每次实例化都会产生多个TableGen定义。如果一个多类继承自另一个多类,那么子多类中的定义就成为当前多类的一部分,就好像它们是在当前多类中声明的一样。

multiclass ro_signed_pats<string T, string Rm, dag Base, dag Offset, dag Extend,
                        dag address, ValueType sty> {
def : Pat<(i32 (!cast<SDNode>("sextload" # sty) address)),
          (!cast<Instruction>("LDRS" # T # "w_" # Rm # "_RegOffset")
            Base, Offset, Extend)>;

def : Pat<(i64 (!cast<SDNode>("sextload" # sty) address)),
          (!cast<Instruction>("LDRS" # T # "x_" # Rm # "_RegOffset")
            Base, Offset, Extend)>;
}

defm : ro_signed_pats<"B", Rm, Base, Offset, Extend,
                      !foreach(decls.pattern, address,
                               !subst(SHIFT, imm_eq0, decls.pattern)),
                      i8>;

 有关TableGen的深入描述,请参阅TableGen Programmer’s Reference

TableGen后端 

如果没有后端,TableGen文件就没有真正的意义。运行*-tblgen时的默认操作是以文本格式打印信息,但这只对调试TableGen文件本身有用。但是,TableGen的强大功能是将源文件解释为可以生成任何您想要的内部表示。

目前TableGen的用途是创建巨大的包含表的文件,这些表既可以直接包含(如果输出是您正在编码的语言),也可以通过包含文件的宏在预处理中使用。

如果后端已经以C格式打印了一个表,或者输出只是一个字符串列表(用于错误和警告消息),那么可以使用直接输出。如果相同的信息需要在不同的上下文中使用(比如指令名),那么应该使用预处理输出,因此后端应该打印一个元信息列表,该列表可以被塑造成不同的编译时格式。

有关可用后端列表,请参阅TableGen BackEnds,有关如何编写和调试新后端的信息,请参阅 TableGen Backend Developer’s Guide

TableGen不足

尽管TableGen非常通用,但它也有一些常被指出的缺陷。常见的主题是,虽然TableGen允许您构建特定领域的语言,但您创建的最终语言缺乏其他DSL的强大功能,这反过来大大增加了TableGen文件的大小和复杂性。

与此同时,TableGen允许您通过定制的后端创建基本概念的任何含义,这可能会破坏原始设计,使新手很难理解晦涩的TableGen文件。

还有一些人支持进一步扩展语义,但要确保后端遵守严格的规则。其他人则建议我们转向为特定目的而设计的更少、更强大的领域特定语言,甚至重用现有的领域特定语言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值