LLVM官方文档阅读(二)-TableGen_Language_Introduction

本文为LLVM官方的一篇TableGen介绍文章https://llvm.org/docs/TableGen/的翻译和阅读笔记,在官方文档的基础上补充一些更便于理解的内容,如有出入,一切以官方文档为主。


前言

“组合和匹配”的方案允许目标作者选择对其架构有意义的内容,并允许跨不同目标进行大量代码重用。这带来了另一个挑战:每个共享组件都需要能够以通用方式推理目标特定属性。例如,共享寄存器分配器需要知道每个目标的寄存器文件以及指令与其寄存器操作数之间存在的约束。LLVM 对此的解决方案是为每个目标提供由 tblgen 工具处理的领域特定的声明语言(一组.td文件)。
上面这段话截自我的另一篇博客 LLVM官方文档阅读(一)LLVM介绍,是LLVM介绍文档中对TableGen的一个简单介绍,阐述了td语言的存在意义。


提示:以下是本篇文章正文内容

一、TableGen介绍


该图为网上找的一个TableGen的介绍汇报上截的一张图,汇报链接在最后面的参考链接中,可以通过看该视频对TableGen建立一个框架再阅读此文,后面同样的引用不再进行说明。

TableGen 的目的是帮助人们开发和维护领域特定(domain-specific)信息的记录,比如说和目标机器平台相关的信息,或者和编程语言相关的信息,它可以帮助开发人员在编译器开发过程中减小冗余代码,降低维护修改难度,以及提供更好的开发框架来有利于扩充信息。

TableGen 前端负责解析文件、实例化定义并将结果交给领域特定的后端进行处理。有关TableGen 的深入说明,请参阅TableGen Programmer’s Reference。有关运行各种 TableGen的*-tblgen 命令的详细信息,请参阅tblgen - Description to C++ Code

TableGen 当前的主要用户是The LLVM Target-Independent Code GeneratorClang diagnostics and attributes

二、TableGen程序

LLVM中有一个工具,叫做llvm-tblgen,可以协助我们更容易的开展TableGen的开发工作。这个工具在正常的编译流程中是不参与的,只是额外的开发工具。它可以用来调度起整个TableGen的工作流。

2.1 运行TableGen

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

$ llvm-tblgen X86.td -print-enums -class=Register

为了有效,必须使用后端。这些后端可在命令行上是可选择的(输入 llvm-tblgen -help 以获取帮助列表)。例如,要获取一个包含所有定义的列表以子类化一个特殊的类型(这对于构建这些记录的枚举列表很有用),请使用以下选项:-print-enums

以下是TableGen读取td文件的两个运行示例,第一个为打印所有寄存器类中enum值的列表信息,第二个为打印出其中所有指令类中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, ...

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

如果您想要使用 TableGen,您很可能必须要编写一个后端来提取您需要的特定信息并且将它以适当的方式格式化。您可以通过在 C++ 中扩展 TableGen 本身,或者可以通过编写脚本来处理JSON形式的输出。

2.2 示例

在没有其他参数的情况下,llvm-tblgen解析指定的文件并打印出所有的类还有所有的定义。这是查看各种完全扩展的定义的好方法。
X86.td文件上运行llvm-tblgen(没有其他参数)会打印出以下内容:

...
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为记录集合的所有数据,string Namespace = "X86" 表示该指令是“X86”命名空间的一部分,Pattern行表示代码生成器如何选择指令,有的行表示它是一个二地址指令,有的行表示它具有特定的编码等。记录信息中的内容和语义特定于X86后端的需求,仅作为示例显示。

如您所见,代码生成器支持的每条指令都需要大量信息,而如果手动指定所有信息将是无法维护的,容易出现错误,而且一开始做起来很累。因为我们使用的是 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))]>;

此定义使用自定义类I(从自定义类X86Inst扩展而来),该类在 X86 特定的 TableGen 文件中定义,以分解出其类的指令共享的公共特性。TableGen 的一个关键特性是它允许最终用户定义他们在描述信息时喜欢使用的抽象。

三、语法

TableGen 的语法大致(loosely)基于 C++ 模板,具有内置类型和规范。此外,TableGen 的语法还引入了一些自动化概念,如 multiclassforeachlet等。

注:本文对语法描述部分较为简单,只描述了TableGen的几个主要组成部分,语法细节在另一个官方文档TableGen Language Reference会有详细描述,我将在后面也详细阅读这篇官方文档。

3.1 基本概念

TableGen 文件由两个关键部分组成:“classes”和“definitions”,两者都被视为“records”。

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

TableGen definitions是“record”的具体形式。这些通常没有任何未定义的值,并用 ’ def’ 关键字标记。如:

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

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

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

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

在这里,类 ProcNoItin 接收字符串类型(string)的参数名称和目标特征列表(list),通过向下传递参数以及硬编码 NoItineraries(即将数据直接写在代码(程序)中)来专业化(specilizing)类 Processor。

TableGen multiclasses 是一组同时实例化的抽象记录。每个实例化可能导致多个 TableGen 定义。如果multiclass继承自另一个multiclass,则子多类中的定义将成为当前multiclass的一部分,就好像它们是在当前multiclass中声明的一样。

以下为我在网上看到的一个很好的multiclass示例,可以帮助理解。
在这里插入图片描述
multiclass可以用于批量生成具有共性的records,如上图所示,在实例化multiclass的时候,只要带上参数,就可以自动生成多个class,可以减少重复的工作量。
有关TableGen 的深入说明,请参阅TableGen Programmer’s Reference

四、TableGen后端

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

当前 TableGen 的用途是创建包含表的巨大的include文件,以至于你可以直接include这些表(如果输出是您正在编码的语言的话),也可以被使用于预处理。

如果后端已经以 C 格式打印表格,或者如果输出只是一个字符串列表(用于错误和警告消息),则可以使用直接输出。如果需要在不同的上下文中使用相同的信息(如指令名称),则应使用预处理输出,因此您的后端应打印一个可以形成不同compile-time格式的元信息列表。

请参阅TableGen Backend Developer’s Guide,了解如何编写和调试新的后端。

五、TableGen的不足

尽管TableGen非常通用,但是仍有一些已被多次指出的不足。其共同点是,虽然 TableGen 允许您构建领域特定的语言,但您创建的最终语言缺乏其他 DSL(领域特定语言) 的能力,这反过来又大大增加了 TableGen 文件的大小和复杂性。

同时,TableGen 允许您通过自定义后端创建基本概念的任何含义,这可能会偏离最初的设计并且会使新手很难理解讨厌的(evil)的 TableGen文件。

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


参考链接

https://llvm.org/docs/TableGen/
https://zhuanlan.zhihu.com/p/141265959
https://www.bilibili.com/video/BV1Tr4y127EH?from=search&seid=17027613356885873368&spm_id_from=333.337.0.0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值