TableGen 后端

简介

TableGen 后端是 TableGen 功能的核心。源文件提供了被解析的类和记录,最终成为记录实例的集合,但后端负责以对用户有意义的方式解释和打印这些记录(通常是 C++ 包含文件或警告、选项和错误消息的文本列表)。

LLVM、Clang 和 MLIR 都使用 TableGen,但目标截然不同。LLVM 使用它来自动生成大量关于指令、调度、核心和架构特性的信息。一些后端生成被多个源文件使用的输出,因此需要以一种易于使用预处理器技巧的方式创建它们。一些后端还可以打印 C++ 代码结构,以便可以直接包含它们。

另一方面,Clang 主要使用它来处理诊断消息(错误、警告、提示)和属性,因此更偏向于文本方面。

MLIR 使用 TableGen 来定义操作、操作方言和操作特性。

请参阅 TableGen 程序员参考 以深入了解 TableGen,以及 TableGen 后端开发人员指南 以获取编写新后端的指南。

LLVM 后端

警告

此部分不完整。下面每个部分都需要三个子部分:描述其目的及其用户的列表,从通用输入生成的输出,以及为什么需要一个新的后端(如果存在类似的东西)。

总的来说,每个后端都将采用相同的 TableGen 文件类型并将其转换为针对不同目标/用途的类似输出。TableGen 文件、后端及其用户之间存在一个隐式契约。

例如,一个全局契约是每个后端都生成宏保护的节。根据文件是被头文件还是源文件包含,甚至是在文件的哪个上下文中使用包含,您必须在包含它之前定义一个宏,才能获得正确的输出

#define GET_REGINFO_TARGET_DESC
#include "ARMGenRegisterInfo.inc"

并且只会包含生成文件的一部分。如果您需要从同一个源 TableGen 文件以多种格式(实例化、初始化、getter/setter 函数等)获取相同的信息,而无需多次编译 TableGen 文件,这将非常有用。

有时,在同一个包含文件之前可能会定义多个宏以输出多个块

#define GET_REGISTER_MATCHER
#define GET_SUBTARGET_FEATURE_NAME
#define GET_MATCHER_IMPLEMENTATION
#include "ARMGenAsmMatcher.inc"

这些宏将在包含文件中使用后自动取消定义。

在所有 LLVM 后端上,llvm-tblgen 二进制文件将在根 TableGen 文件 <Target>.td 上执行,该文件应包含所有其他文件。这保证了所有必要的信息都可访问,并且 TableGen 文件中不需要重复。

代码发射器

目的:CodeEmitterGen 使用指令及其字段的描述来构建一个自动代码发射器:一个函数,给定一个 MachineInstr,返回指令的(当前,32 位无符号)值。

输出:C++ 代码,通过覆盖虚拟函数作为 <Target>CodeEmitter::function() 来实现目标的 CodeEmitter 类。

用法:用于直接包含在 <Target>MCCodeEmitter.cpp 的末尾。

寄存器信息

目的:此 TableGen 后端负责为代码生成器发出目标寄存器文件的描述。它使用 Register、RegisterAliases 和 RegisterClass 类的实例来收集此信息。

输出:C++ 代码,其中包含枚举和结构,表示寄存器映射、属性、掩码等。

用法:同时在 <Target>BaseRegisterInfo<Target>MCTargetDesc(头文件和源文件)中使用宏来定义它们是在声明还是初始化问题中使用。

指令信息

目的:此 TableGen 后端负责为代码生成器发出目标指令集的描述。(与代码发射器有什么区别?)

输出:C++ 代码,其中包含枚举和结构,表示指令映射、属性、掩码等。

用法:同时在 <Target>BaseInstrInfo<Target>MCTargetDesc(头文件和源文件)中使用宏来定义它们是在声明还是初始化问题中使用。

汇编编写器

目的:为当前目标发出汇编打印器。

输出:除了其他内容外,还实现了 <Target>InstPrinter::printInstruction()

用法:直接包含到 InstPrinter/<Target>InstPrinter.cpp 中。

汇编匹配器

目的:发出目标说明符匹配器,用于转换 MCInst 结构中已解析的汇编操作数。它还发出用于自定义操作数解析的匹配器。AsmMatcherEmitter.cpp 文件中编写了大量文档。

输出:汇编程序解析器的匹配器函数、声明等。

用法:在后端的 AsmParser/<Target>AsmParser.cpp 中用于构建 AsmParser 类。

反汇编器

目的:包含各种架构的反汇编表发射器。DisassemblerEmitter.cpp 文件中编写了大量文档。

输出:解码表、静态解码函数等。

用法:直接包含在 Disassembler/<Target>Disassembler.cpp 中,以满足所有默认解码,在所有手工解码之后。

伪指令降低

目的:生成伪指令降低。

输出:实现 <Target>AsmPrinter::emitPseudoExpansionLowering()

用法:直接包含到 <Target>AsmPrinter.cpp 中。

调用约定

目的:负责发出此目标支持的调用约定的描述。

输出:实现静态函数以处理通过匹配样式链接的调用约定,在没有匹配时返回 false。

用法:在 ISelLowering 和 FastIsel 中用作函数指针,以指向由 CC 选择函数返回的实现。

DAG 指令选择器

目的:生成 DAG 指令选择器。

输出:创建用于自动执行 DAG 选择的大型函数。

用法:包含在 <Target>ISelDAGToDAG.cpp 中,位于目标的 SelectionDAGISel 实现内部。

DFA 包裹器

目的:此类解析 Schedule.td 文件并生成一个 API,可用于推断是否可以将指令添加到 VLIW 架构上的数据包中。该类在内部生成一个确定性有限自动机 (DFA),该自动机模拟了将指令添加到数据包时机器指令到功能单元的所有可能映射。

输出:GPU 后端(Hexagon、AMD)的调度表。

用法:直接包含在 <Target>InstrInfo.cpp 中。

快速指令选择器

目的:此 TableGen 后端发出代码供“快速”指令选择算法使用。有关背景信息,请参阅 lib/CodeGen/SelectionDAG/FastISel.cpp 顶部的注释。此文件扫描目标的 TableGen 指令信息文件并提取具有明显模式的指令,并发出代码以按类型和运算符查找这些指令。

输出:生成 PredicateFastEmit 方法。

用途:实现目标的 FastISel 类实现中的私有方法。

子目标

目的:生成子目标枚举。

输出:枚举、全局变量、子目标信息的局部表。

用途:填充 <Target>SubtargetMCTargetDesc/<Target>MCTargetDesc 文件(头文件和源文件)。

内联函数

目的:生成(目标)内联函数信息。

OptParserDefs

目的:打印类的枚举值。

可搜索表

目的:生成自定义的可搜索表。

输出:枚举、全局表和查找辅助函数。

用途:此后端允许从 TableGen 记录生成自由格式的目标特定表。ARM 和 AArch64 目标使用此后端生成系统寄存器的表;AMDGPU 目标使用它生成有关复杂图像和内存缓冲区指令的元数据。

有关详细说明,请参阅 可搜索表参考

CTags

目的:此 tablegen 后端发出 ctags(1) 格式的定义索引。辅助脚本 utils/TableGen/tdtags 提供了一个更易于使用的界面;运行“tdtags -H”以获取文档。

X86EVEX2VEX

目的:此 X86 特定 tablegen 后端发出将 EVEX 编码指令映射到其 VEX 编码相同指令的表。

Clang 后端

ClangAttrClasses

目的:创建 Attrs.inc,其中包含 Attr.td 中任何未设置 ASTNode = 0 的属性的语义属性类声明。此文件包含在 Attr.h 的一部分中。

ClangAttrParserStringSwitches

目的:创建 AttrParserStringSwitches.inc,其中包含与解析器相关的字符串切换的 StringSwitch::Case 语句。每个切换都使用自己的宏(例如 CLANG_ATTR_ARG_CONTEXT_LISTCLANG_ATTR_IDENTIFIER_ARG_LIST),在包含 AttrParserStringSwitches.inc 之前应定义该宏,并在之后取消定义。

ClangAttrImpl

目的:创建 AttrImpl.inc,其中包含 Attr.td 中任何未设置 ASTNode = 0 的属性的语义属性类定义。此文件包含在 AttrImpl.cpp 的一部分中。

ClangAttrList

目的:创建 AttrList.inc,当需要语义属性标识符列表时使用。例如,AttrKinds.h 包含此文件以生成 attr::Kind 枚举值的列表。此列表被分成多个类别:属性、可继承属性和可继承参数属性。此分类根据 Attr.td 中的信息自动进行,并用于实现 dyn_cast 和类似 API 所需的 classof 功能。

ClangAttrPCHRead

目的:创建 AttrPCHRead.inc,用于在 ASTReader::ReadAttributes 函数中反序列化属性。

ClangAttrPCHWrite

目的:创建 AttrPCHWrite.inc,用于在 ASTWriter::WriteAttributes 函数中序列化属性。

ClangAttrSpellings

目的:创建 AttrSpellings.inc,用于实现 __has_attribute 特性测试宏。

ClangAttrSpellingListIndex

目的:创建 AttrSpellingListIndex.inc,用于将解析的属性拼写(包括使用的语法或范围)映射到属性拼写列表索引。这些拼写列表索引值是通过 AttributeList::getAttributeSpellingListIndex 公开的内部实现细节。

ClangAttrVisitor

目的:创建 AttrVisitor.inc,用于实现递归 AST 访问器。

ClangAttrTemplateInstantiate

目的:创建 AttrTemplateInstantiate.inc,实现 instantiateTemplateAttribute 函数,在实例化需要克隆属性的模板时使用。

ClangAttrParsedAttrList

目的:创建 AttrParsedAttrList.inc,用于生成 AttributeList::Kind 解析的属性枚举。

ClangAttrParsedAttrImpl

目的:创建 AttrParsedAttrImpl.inc,由 AttributeList.cpp 用于实现 AttributeList 类上的几个函数。此功能通过 AttrInfoMap ParsedAttrInfo 数组实现,该数组为每个解析的属性对象包含一个元素。

ClangAttrParsedAttrKinds

目的:创建 AttrParsedAttrKinds.inc,用于实现 AttributeList::getKind 函数,将字符串(和语法)映射到解析的属性 AttributeList::Kind 枚举。

ClangAttrDump

目的:创建 AttrDump.inc,用于转储有关属性的信息。它用于实现 ASTDumper::dumpAttr

ClangDiagsDefs

生成 Clang 诊断定义。

ClangDiagGroups

生成 Clang 诊断组。

ClangDiagsIndexName

生成 Clang 诊断名称索引。

ClangCommentNodes

生成 Clang AST 注释节点。

ClangDeclNodes

生成 Clang AST 声明节点。

ClangStmtNodes

生成 Clang AST 语句节点。

ClangSACheckers

生成 Clang 静态分析检查器。

ClangCommentHTMLTags

为文档注释中使用的 HTML 标记名称生成高效的匹配器。

ClangCommentHTMLTagsProperties

为 HTML 标记属性生成高效的匹配器。

ClangCommentHTMLNamedCharacterReferences

生成将命名字符引用转换为 UTF-8 序列的函数。

ClangCommentCommandInfo

为文档注释中使用的命令生成命令属性。

ClangCommentCommandList

生成文档注释中使用的命令列表。

ArmNeon

为 clang 生成 arm_neon.h。

ArmNeonSema

为 clang 生成 ARM NEON 语义支持。

ArmNeonTest

为 clang 生成 ARM NEON 测试。

AttrDocs

目的:从 AttrDocs.td 创建 AttributeReference.rst,用于记录面向用户的属性。

通用后端

JSON 参考

目的:将每个 def 中的所有值输出为 JSON 数据结构,以便各种语言轻松解析。对于编写自定义后端而无需修改 TableGen 本身,或对传递给内置后端的相同 TableGen 数据执行辅助分析很有用。

输出:

输出文件的根是一个 JSON 对象(即字典),包含以下固定键

  • !tablegen_json_version:一个数字版本字段,如果对该数据结构进行不兼容的更改,则该字段将增加。此处描述的格式对应于版本 1。

  • !instanceof:一个字典,其键是在 TableGen 输入中定义的类名。对于每个键,对应值是一个字符串数组,给出派生自该类的 def 记录的名称。例如,root["!instanceof"]["Instruction"] 将列出所有派生自类 Instruction 的记录的名称。

对于每个 def 记录,根对象也具有记录名称的键。对应值是一个辅助对象,包含以下固定键

  • !superclasses:一个字符串数组,给出此记录派生自的所有类的名称。

  • !fields:一个字符串数组,给出此记录中所有使用 field 关键字定义的变量的名称。

  • !name:一个字符串,给出记录的名称。这始终与 JSON 根对象中对应于此记录字典的键相同。(如果记录是匿名的,则名称是任意的。)

  • !anonymous:一个布尔值,指示记录的名称是由 TableGen 输入指定(如果为 false),还是由 TableGen 本身发明(如果为 true)。

  • !locs:一个字符串数组,给出与此记录关联的源位置。对于从 multiclass 实例化的记录,这将给出每个 defdefm 的位置,从最内部的 multiclass 开始,到顶层的 defm 结束。每个字符串包含文件名和行号,用冒号分隔。

对于在记录中定义的每个变量,该记录的 def 对象也具有变量名称的键。对应值是变量值的 JSON 翻译,使用下面描述的约定。

某些 TableGen 数据类型直接转换为相应的 JSON 类型

  • 完全未定义的值(例如,对于在某些记录的超类中声明但没有初始化程序的变量,并且从未由记录本身或任何其他超类初始化的变量)将作为 JSON null 值发出。

  • intbit 值将作为数字发出。请注意,TableGen int 值能够保存过大而无法在 IEEE 双精度中精确表示的整数。JSON 输出中的整数文字将显示完整的精确整数值。因此,如果您需要以完全精度检索大整数,则应使用能够将此类文字转换回 64 位整数而不会损失精度的 JSON 读取器,例如 Python 的标准 json 模块。

  • stringcode 值将作为 JSON 字符串发出。

  • list<T> 值(对于任何元素类型 T)将作为 JSON 数组发出。数组的每个元素依次使用相同的约定表示。

  • bits 值也作为数组发出。一个 bits 数组按从最低有效位到最高有效位的顺序排列。因此,索引为 i 的元素对应于在 TableGen 源代码中描述为 x{i} 的位。但是,请注意,这意味着脚本语言可能会以与在 TableGen 源代码或诊断 -print-records 输出中显示的方式相反的顺序显示数组。

所有其他 TableGen 值类型都作为 JSON 对象发出,包含两个标准字段:kind 是一个区分符,描述对象表示哪种值,printable 是一个字符串,给出与 -print-records 中显示的值相同的表示形式。

  • def 对象的引用具有 kind=="def",并且具有一个额外的字段 def,给出所引用对象的名称。

  • 对同一记录中另一个变量的引用具有 kind=="var",并且具有一个额外的字段 var,给出所引用变量的名称。

  • 对同一记录中 bits 类型变量的特定位的引用具有 kind=="varbit",并且有两个额外的字段:var 给出所引用变量的名称,index 给出位的索引。

  • 类型为 dag 的值具有 kind=="dag",并且有两个额外的字段。operator 给出 dag 初始化程序的左括号后的初始值;args 是一个数组,给出后续参数。 args 的元素是长度为 2 的数组,给出每个参数的值及其冒号后缀名称(如果有)。例如,在 dag 值 (Op 22, "hello":$foo) 的 JSON 表示中(假设 Op 是在其他地方使用 def 语句定义的记录的名称)

    • operator 将是一个对象,其中 kind=="def"def=="Op"

    • args 将是数组 [[22, null], ["hello", "foo"]]

  • 如果输出中出现任何其他类型的值或复杂表达式,则它将具有 kind=="complex",并且没有其他字段。后端预计不需要这些值。标准 printable 字段可用于在必要时提取它们的 TableGen 源语法表示形式。

可搜索表参考

TableGen 包含文件 SearchableTable.td 提供用于生成 C++ 可搜索表的类。这些表在以下部分中进行了描述。要生成 C++ 代码,请使用 --gen-searchable-tables 选项运行 llvm-tblgen,这将调用从您提供的记录生成表的后台程序。

为可搜索表生成的每个数据结构都受 #ifdef 保护。这允许您包含生成的 .inc 文件并仅选择某些数据结构以供包含。下面的示例显示了这些保护中使用的宏名称。

通用枚举类型

GenericEnum 类使定义 C++ 枚举类型和该类型的枚举元素变得容易。要定义类型,请定义一个记录,其父类为 GenericEnum,其名称为所需的枚举类型。此类提供三个字段,您可以使用 let 语句在记录中设置这些字段。

  • string FilterClass。枚举类型将为派生自此类的每个记录包含一个元素。收集这些记录以组装完整的元素集。

  • string NameField在收集的记录中一个字段的名称,该字段指定元素的名称。如果记录没有这样的字段,则将使用记录的名称。

  • string ValueField在收集的记录中一个字段的名称,该字段指定元素的数值。如果记录没有这样的字段,则将为其分配一个整数值。值从 0 开始按字母顺序分配。

这是一个示例,其中元素的值是显式指定的,作为 BEntry 类的模板参数。显示了生成的 C++ 代码。

def BValues : GenericEnum {
  let FilterClass = "BEntry";
  let NameField = "Name";
  let ValueField = "Encoding";
}

class BEntry<bits<16> enc> {
  string Name = NAME;
  bits<16> Encoding = enc;
}

def BFoo   : BEntry<0xac>;
def BBar   : BEntry<0x14>;
def BZoo   : BEntry<0x80>;
def BSnork : BEntry<0x4c>;
#ifdef GET_BValues_DECL
enum BValues {
  BBar = 20,
  BFoo = 172,
  BSnork = 76,
  BZoo = 128,
};
#endif

在以下示例中,元素的值会自动分配。请注意,值从 0 开始分配,按元素名称的字母顺序分配。

def CEnum : GenericEnum {
  let FilterClass = "CEnum";
}

class CEnum;

def CFoo : CEnum;
def CBar : CEnum;
def CBaz : CEnum;
#ifdef GET_CEnum_DECL
enum CEnum {
  CBar = 0,
  CBaz = 1,
  CFoo = 2,
};
#endif

通用表

GenericTable 类用于定义可搜索的通用表。TableGen 生成 C++ 代码以定义表条目,并生成用于根据主键搜索表的函数的声明和定义。要定义表,请定义一个记录,其父类为 GenericTable,其名称为条目的全局表名称。此类提供六个字段。

  • string FilterClass。表将为派生自此类的每个记录包含一个条目。

  • string FilterClassField。这是一个可选的 FilterClass 字段,应为 bit 类型。如果指定,则只有该字段为真的记录才会在表中具有相应的条目。如果该字段未包含在 Fields 列表中,则不会将其包含在生成的 C++ 字段中。

  • string CppTypeName。存储条目的表的 C++ 结构体/类类型的名称。如果未指定,则使用 FilterClass 名称。

  • list<string> Fields。包含表条目数据的收集记录中字段名称的列表。此列表的顺序决定了 C++ 初始化程序中值的顺序。有关这些字段的类型信息,请参见下文。

  • list<string> PrimaryKey。构成主键的字段列表。

  • string PrimaryKeyName。执行主键查找的生成的 C++ 函数的名称。

  • bit PrimaryKeyEarlyOut。请参见下面的第三个示例。

  • bit PrimaryKeyReturnRange。设置为 1 时,会修改查找函数的定义,使其返回一系列结果,而不是指向对象的单个指针。当多个对象满足查找函数指定的条件时,此功能非常有用。目前,它仅支持主键查找函数。有关更多详细信息,请参阅下面的第二个示例。

TableGen 尝试推断每个表字段的类型,以便它能够格式化在输出表中发出的 C++ 初始化程序。它可以推断 bitbits<n>stringIntrinsicInstruction。这些可以在主键中使用。必须显式指定任何其他字段类型;这可以通过下面第二个示例中所示的方式完成。此类字段不能用于主键。

字段类型的一种特殊情况与代码有关。任意代码由字符串表示,但必须作为 C++ 初始化程序发出,不带引号。如果代码字段是使用代码字面量 ([{...}]) 定义的,则 TableGen 将知道不带引号地发出它。但是,如果它是使用字符串字面量或复杂字符串表达式定义的,则 TableGen 将不知道。在这种情况下,您可以通过在 GenericTable 记录中包含以下行来强制 TableGen 将字段视为代码,其中 *xxx* 是代码字段名称。

string TypeOf_xxx = "code";

这是一个 TableGen 可以推断字段类型的示例。请注意,表条目记录是匿名的;条目记录的名称无关紧要。

def ATable : GenericTable {
  let FilterClass = "AEntry";
  let FilterClassField = "IsNeeded";
  let Fields = ["Str", "Val1", "Val2"];
  let PrimaryKey = ["Val1", "Val2"];
  let PrimaryKeyName = "lookupATableByValues";
}

class AEntry<string str, int val1, int val2, bit isNeeded> {
  string Str = str;
  bits<8> Val1 = val1;
  bits<10> Val2 = val2;
  bit IsNeeded = isNeeded;
}

def : AEntry<"Bob",   5, 3, 1>;
def : AEntry<"Carol", 2, 6, 1>;
def : AEntry<"Ted",   4, 4, 1>;
def : AEntry<"Alice", 4, 5, 1>;
def : AEntry<"Costa", 2, 1, 1>;
def : AEntry<"Dale",  2, 1, 0>;

这是生成的 C++ 代码。 lookupATableByValues 的声明受 GET_ATable_DECL 保护,而定义受 GET_ATable_IMPL 保护。

#ifdef GET_ATable_DECL
const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2);
#endif

#ifdef GET_ATable_IMPL
constexpr AEntry ATable[] = {
  { "Costa", 0x2, 0x1 }, // 0
  { "Carol", 0x2, 0x6 }, // 1
  { "Ted", 0x4, 0x4 }, // 2
  { "Alice", 0x4, 0x5 }, // 3
  { "Bob", 0x5, 0x3 }, // 4
  /* { "Dale", 0x2, 0x1 }, // 5 */ // We don't generate this line as `IsNeeded` is 0.
};

const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2) {
  struct KeyType {
    uint8_t Val1;
    uint16_t Val2;
  };
  KeyType Key = { Val1, Val2 };
  auto Table = ArrayRef(ATable);
  auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
    [](const AEntry &LHS, const KeyType &RHS) {
      if (LHS.Val1 < RHS.Val1)
        return true;
      if (LHS.Val1 > RHS.Val1)
        return false;
      if (LHS.Val2 < RHS.Val2)
        return true;
      if (LHS.Val2 > RHS.Val2)
        return false;
      return false;
    });

  if (Idx == Table.end() ||
      Key.Val1 != Idx->Val1 ||
      Key.Val2 != Idx->Val2)
    return nullptr;
  return &*Idx;
}
#endif

ATable 中的表条目按 Val1 的顺序排序,并在每个值内按 Val2 排序。这允许对表进行二分查找,该查找由 std::lower_bound 在查找函数中执行。查找函数返回对找到的表条目的引用,如果未找到条目,则返回空指针。如果表具有一个主键字段,该字段是整数且编号密集,则会生成直接查找而不是二分查找。

此示例包含一个 TableGen 无法推断其类型的字段。 Kind 字段使用上面定义的枚举类型 CEnum。为了告知 TableGen 类型,派生自 GenericTable 的记录必须包含一个名为 TypeOf_*field* 的字符串字段,其中 *field* 是需要类型的字段的名称。

def CTable : GenericTable {
  let FilterClass = "CEntry";
  let Fields = ["Name", "Kind", "Encoding"];
  string TypeOf_Kind = "CEnum";
  let PrimaryKey = ["Encoding"];
  let PrimaryKeyName = "lookupCEntryByEncoding";
}

class CEntry<string name, CEnum kind, int enc> {
  string Name = name;
  CEnum Kind = kind;
  bits<16> Encoding = enc;
}

def : CEntry<"Apple", CFoo, 10>;
def : CEntry<"Pear",  CBaz, 15>;
def : CEntry<"Apple", CBar, 13>;

这是生成的 C++ 代码。

#ifdef GET_CTable_DECL
const CEntry *lookupCEntryByEncoding(uint16_t Encoding);
#endif

#ifdef GET_CTable_IMPL
constexpr CEntry CTable[] = {
  { "Apple", CFoo, 0xA }, // 0
  { "Apple", CBar, 0xD }, // 1
  { "Pear", CBaz, 0xF }, // 2
};

const CEntry *lookupCEntryByEncoding(uint16_t Encoding) {
  struct KeyType {
    uint16_t Encoding;
  };
  KeyType Key = { Encoding };
  auto Table = ArrayRef(CTable);
  auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
    [](const CEntry &LHS, const KeyType &RHS) {
      if (LHS.Encoding < RHS.Encoding)
        return true;
      if (LHS.Encoding > RHS.Encoding)
        return false;
      return false;
    });

  if (Idx == Table.end() ||
      Key.Encoding != Idx->Encoding)
    return nullptr;
  return &*Idx;
}

在上面的示例中,让我们再添加一条记录,其编码与记录 CEntry<"Pear",  CBaz, 15> 的编码相同。

def CFoobar : CEnum;
def : CEntry<"Banana", CFoobar, 15>;

以下是新生成的 CTable

#ifdef GET_Table_IMPL
constexpr CEntry Table[] = {
  { "Apple", CFoo, 0xA }, // 0
  { "Apple", CBar, 0xD }, // 1
  { "Banana", CFoobar, 0xF }, // 2
  { "Pear", CBaz, 0xF }, // 3
};

由于 Banana 在字典序上首先出现,因此在 CEntry 表中,名称为 Banana 的记录将出现在名称为 Pear 的记录之前。因此,lookupCEntryByEncoding 函数将始终返回指向名称为 Banana 的记录的指针,即使在某些情况下正确的结果可能是名称为 Pear 的记录。此类场景使现有的查找函数不足,因为它们始终返回指向表中单个条目的指针,但相反,它应该返回一系列结果,因为多个条目匹配查找函数寻求的条件。在这种情况下,需要修改查找函数的定义以返回一系列结果,这可以通过设置 PrimaryKeyReturnRange 来完成。

def CTable : GenericTable {
  let FilterClass = "CEntry";
  let Fields = ["Name", "Kind", "Encoding"];
  string TypeOf_Kind = "CEnum";
  let PrimaryKey = ["Encoding"];
  let PrimaryKeyName = "lookupCEntryByEncoding";
  let PrimaryKeyReturnRange = true;
}

这是修改后的查找函数。

llvm::iterator_range<const CEntry *> lookupCEntryByEncoding(uint16_t Encoding) {
  struct KeyType {
    uint16_t Encoding;
  };
  KeyType Key = {Encoding};
  struct Comp {
    bool operator()(const CEntry &LHS, const KeyType &RHS) const {
      if (LHS.Encoding < RHS.Encoding)
        return true;
      if (LHS.Encoding > RHS.Encoding)
        return false;
      return false;
    }
    bool operator()(const KeyType &LHS, const CEntry &RHS) const {
      if (LHS.Encoding < RHS.Encoding)
        return true;
      if (LHS.Encoding > RHS.Encoding)
        return false;
      return false;
    }
  };
  auto Table = ArrayRef(Table);
  auto It = std::equal_range(Table.begin(), Table.end(), Key, Comp());
  return llvm::make_range(It.first, It.second);
}

新的查找函数将返回一个迭代器范围,第一个指针指向第一个结果,最后一个指针指向表中最后一个匹配的结果。但是,请注意,仅支持为 PrimaryKeyName 发出修改后的定义。

PrimaryKeyEarlyOut 字段在设置为 1 时,会修改查找函数,使其测试主键的第一个字段以确定它是否在收集记录的主键范围内。如果不是,则函数返回空指针,而不执行二分查找。这对于仅为较大基于枚举的空间的某些元素提供数据的表很有用。主键的第一个字段必须是整数类型;它不能是字符串。

let PrimaryKeyEarlyOut = 1 添加到上面的 ATable

def ATable : GenericTable {
  let FilterClass = "AEntry";
  let Fields = ["Str", "Val1", "Val2"];
  let PrimaryKey = ["Val1", "Val2"];
  let PrimaryKeyName = "lookupATableByValues";
  let PrimaryKeyEarlyOut = 1;
}

会导致查找函数如下所示更改

const AEntry *lookupATableByValues(uint8_t Val1, uint16_t Val2) {
  if ((Val1 < 0x2) ||
      (Val1 > 0x5))
    return nullptr;

  struct KeyType {
  ...

我们可以使用相同的 FilterClass 构造两个 GenericTables,以便它们从同一组总体记录中进行选择,但为它们分配不同的 FilterClassField 值,以便它们包含该类的不同子集的记录。

例如,我们可以创建两个表,它们仅包含偶数或奇数记录。字段 IsEvenIsOdd 不会包含在生成的 C++ 字段中,因为它们未包含在 Fields 列表中。

class EEntry<bits<8> value> {
  bits<8> Value = value;
  bit IsEven = !eq(!and(value, 1), 0);
  bit IsOdd = !not(IsEven);
}

foreach i = {1-10} in {
  def : EEntry<i>;
}

def EEntryEvenTable : GenericTable {
  let FilterClass = "EEntry";
  let FilterClassField = "IsEven";
  let Fields = ["Value"];
  let PrimaryKey = ["Value"];
  let PrimaryKeyName = "lookupEEntryEvenTableByValue";
}

def EEntryOddTable : GenericTable {
  let FilterClass = "EEntry";
  let FilterClassField = "IsOdd";
  let Fields = ["Value"];
  let PrimaryKey = ["Value"];
  let PrimaryKeyName = "lookupEEntryOddTableByValue";
}

生成的表为

constexpr EEntry EEntryEvenTable[] = {
  { 0x2 }, // 0
  { 0x4 }, // 1
  { 0x6 }, // 2
  { 0x8 }, // 3
  { 0xA }, // 4
};

constexpr EEntry EEntryOddTable[] = {
  { 0x1 }, // 0
  { 0x3 }, // 1
  { 0x5 }, // 2
  { 0x7 }, // 3
  { 0x9 }, // 4
};

搜索索引

SearchIndex 类用于为通用表定义其他查找函数。要定义其他函数,请定义一个记录,其父类为 SearchIndex,其名称为所需查找函数的名称。此类提供三个字段。

  • GenericTable Table。要接收另一个查找函数的表的名称。

  • list<string> Key。构成辅助键的字段列表。

  • bit EarlyOut。请参见通用表中的第三个示例。

  • bit ReturnRange。请参见通用表中的第二个示例。

这是一个添加到上面 CTable 的辅助键示例。生成的函数根据 NameKind 字段查找条目。

def lookupCEntry : SearchIndex {
  let Table = CTable;
  let Key = ["Name", "Kind"];
}

SearchIndex 的使用会生成以下其他 C++ 代码。

const CEntry *lookupCEntry(StringRef Name, unsigned Kind);

...

const CEntry *lookupCEntryByName(StringRef Name, unsigned Kind) {
  struct IndexType {
    const char * Name;
    unsigned Kind;
    unsigned _index;
  };
  static const struct IndexType Index[] = {
    { "APPLE", CBar, 1 },
    { "APPLE", CFoo, 0 },
    { "PEAR", CBaz, 2 },
  };

  struct KeyType {
    std::string Name;
    unsigned Kind;
  };
  KeyType Key = { Name.upper(), Kind };
  auto Table = ArrayRef(Index);
  auto Idx = std::lower_bound(Table.begin(), Table.end(), Key,
    [](const IndexType &LHS, const KeyType &RHS) {
      int CmpName = StringRef(LHS.Name).compare(RHS.Name);
      if (CmpName < 0) return true;
      if (CmpName > 0) return false;
      if ((unsigned)LHS.Kind < (unsigned)RHS.Kind)
        return true;
      if ((unsigned)LHS.Kind > (unsigned)RHS.Kind)
        return false;
      return false;
    });

  if (Idx == Table.end() ||
      Key.Name != Idx->Name ||
      Key.Kind != Idx->Kind)
    return nullptr;
  return &CTable[Idx->_index];
}