扩展 LLVM:添加指令、内联函数、类型等

引言和警告

在使用 LLVM 的过程中,您可能希望为您的研究项目或实验对其进行定制。此时,您可能会意识到需要向 LLVM 添加某些内容,无论是新的基本类型、新的内联函数还是全新的指令。

当您意识到这一点时,请停下来思考一下。您真的需要扩展 LLVM 吗?它是否是 LLVM 在其当前版本中不支持的新基本功能,或者它是否可以从已经存在的 LLVM 元素中合成?如果您不确定,请在 LLVM 论坛 上提问。原因是扩展 LLVM 会变得很复杂,因为您需要更新打算与扩展一起使用的所有不同的 Pass,并且有 许多 LLVM 分析和转换,因此可能需要相当多的工作。

添加 内联函数 比添加指令要容易得多,并且对优化 Pass 是透明的。如果您的新增功能可以表示为函数调用,则内联函数是 LLVM 扩展的首选方法。

在您投入大量精力进行非平凡的扩展之前,**请在列表中询问**您想要执行的操作是否可以使用现有的基础设施完成,或者可能其他人是否已经在进行这项工作。这样做可以节省您大量的时间和精力。

添加新的内联函数

向 LLVM 添加新的内联函数比添加新的指令要容易得多。几乎所有对 LLVM 的扩展都应该从内联函数开始,然后在必要时将其转换为指令。

  1. llvm/docs/LangRef.html:

    记录内联函数。确定它是否是代码生成器特定的,以及限制是什么。与其他人讨论它,以便您确定这是一个好主意。

  2. llvm/include/llvm/IR/Intrinsics*.td:

    为您的内联函数添加一个条目。描述其用于优化的内存访问特性(这控制着它是否会被删除、公共子表达式消除等)。如果任何参数需要是立即数,则必须使用 ImmArg 属性指示这些参数。请注意,任何对参数或返回类型使用 llvm_any*_ty 类型的内联函数都将被 tblgen 视为重载,并且需要在内联函数名称上使用相应的后缀。

  3. llvm/lib/Analysis/ConstantFolding.cpp:

    如果可以对您的内联函数进行常量折叠,请在 canConstantFoldCallToConstantFoldCall 函数中添加对它的支持。

  4. llvm/test/*:

    为您的测试用例添加测试用例到测试套件中

一旦内联函数被添加到系统中,您必须为其添加代码生成支持。通常,您必须执行以下步骤

lib/Target/*/*.td 中,为所选目标添加对 .td 文件的支持。

这通常是向 .td 文件添加匹配内联函数的模式的问题,但它显然可能需要添加您想要生成的指令。PowerPC 和 X86 后端有很多示例可以参考。

添加新的 SelectionDAG 节点

与内联函数一样,向 LLVM 添加新的 SelectionDAG 节点比添加新的指令要容易得多。添加新节点通常是为了帮助表示许多目标共有的指令。这些节点通常映射到 LLVM 指令(add、sub)或内联函数(byteswap、population count)。在其他情况下,添加新节点是为了允许许多目标执行常见任务(在浮点数和整数表示之间转换)或在一个节点中捕获更复杂的行为(旋转)。

  1. include/llvm/CodeGen/ISDOpcodes.h:

    为新的 SelectionDAG 节点添加一个枚举值。

  2. lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp:

    添加代码以将节点打印到 getOperationName。如果您的新节点可以在编译时根据常量参数进行计算(例如常量与另一个常量的加法),请查找采用适当数量参数的 getNode 方法,并在执行对采用与新节点相同数量参数的节点进行常量折叠的 switch 语句中为您的节点添加一个 case。

  3. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    添加代码以根据需要 合法化、提升和扩展 节点。至少,您需要在 LegalizeOp 中为您的节点添加一个 case 语句,该语句会对节点的操作数调用 LegalizeOp,并在任何操作数由于合法化而发生更改时返回一个新节点。SelectionDAG 框架支持的所有目标可能并非都原生支持新节点。在这种情况下,您还必须在 LegalizeOp 中节点的 case 语句中添加代码以将节点扩展为更简单、合法的操作。扩展余数为除法、乘法和减法的 ISD::UREM 的 case 就是一个很好的例子。

  4. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    如果目标可能仅在某些大小下支持正在添加的新节点,那么您还需要在 LegalizeOp 中节点的 case 语句中添加代码以将节点的操作数提升到更大的大小,并执行正确的操作。您还需要添加代码到 PromoteOp 中来执行此操作。有关一个很好的例子,请参阅 ISD::BSWAP,它将操作数提升到更宽的大小,执行字节交换,然后将正确的字节右移以在更宽的类型中模拟较窄的字节交换。

  5. lib/CodeGen/SelectionDAG/LegalizeDAG.cpp:

    ExpandOp 中为您的节点添加一个 case,以教合法化程序如何在已拆分为高半部分和低半部分的值上执行新节点表示的操作。此 case 将用于在 32 位目标上支持具有 64 位操作数的节点。

  6. lib/CodeGen/SelectionDAG/DAGCombiner.cpp:

    如果您的节点可以与自身或其他现有节点以窥孔方式组合,请为其添加一个访问函数,并从该函数调用它。您可以执行一些简单的组合,有很多很好的例子;visitFABSvisitSRL 是良好的起点。

  7. lib/Target/PowerPC/PPCISelLowering.cpp:

    每个目标都有 TargetLowering 类的实现,通常在其自己的文件中(尽管某些目标将其包含在与 DAGToDAGISel 相同的文件中)。目标的默认行为是假设新节点对于该目标所有合法类型都是合法的。如果此目标不原生支持您的节点,则告诉目标提升它(如果它在更大的类型中受支持)或扩展它。这将导致您在上面的 LegalizeOp 中编写的代码将新节点分解为该目标的其他合法节点。

  8. include/llvm/Target/TargetSelectionDAG.td:

    LLVM 支持的大多数当前目标使用 DAGToDAG 方法生成代码,其中 SelectionDAG 节点与目标特定的节点匹配,目标特定的节点表示单个指令。为了使目标将指令与新节点匹配,您必须在此文件中为该节点添加一个 def 到列表中,并使用适当的类型约束。请查看 addbswapfadd 以获取示例。

  9. lib/Target/PowerPC/PPCInstrInfo.td:

    每个目标都有一个 tablegen 文件,描述目标的指令集。对于使用 DAGToDAG 指令选择框架的目标,请为新节点添加一个使用一个或多个目标节点的模式。对此的文档现在有点稀疏,但有一些不错的示例。请参阅 PPCInstrInfo.tdrotl 的模式。

  10. 待办事项:记录复杂模式。

  11. llvm/test/CodeGen/*:

    将新节点的测试用例添加到测试套件中。llvm/test/CodeGen/X86/bswap.ll 是一个很好的例子。

添加新的指令

警告

添加指令会更改 bitcode 格式,并且维护与先前版本的兼容性需要一些工作。仅当绝对必要时才添加指令。

  1. llvm/include/llvm/IR/Instruction.def:

    为指令添加一个编号和一个枚举名称

  2. llvm/include/llvm/IR/Instructions.h:

    为将表示指令的类添加一个定义

  3. llvm/include/llvm/IR/InstVisitor.h:

    为新的指令类型添加访问者的原型

  4. llvm/lib/AsmParser/LLLexer.cpp:

    添加一个新令牌以从汇编文本文件中解析指令

  5. llvm/lib/AsmParser/LLParser.cpp:

    添加关于如何读取指令以及它将构建什么作为结果的语法

  6. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    为指令添加一个 case 以及如何从 bitcode 中解析它

  7. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    为指令添加一个 case 以及如何从 bitcode 中解析它

  8. llvm/lib/IR/Instruction.cpp:

    添加一个 case,说明如何将指令打印到汇编中

  9. llvm/lib/IR/Instructions.cpp:

    实现您在 llvm/include/llvm/Instructions.h 中定义的类

  10. 测试指令

  11. llvm/lib/Target/*:

    为代码生成器添加对指令的支持,或添加一个降低 Pass。

  12. llvm/test/*:

    将测试用例添加到测试套件中。

此外,您需要实现(或修改)任何您希望理解此新指令的分析或 Pass。

添加新的类型

警告

添加新类型会更改 bitcode 格式,并将破坏与当前存在的 LLVM 安装的兼容性。仅当绝对必要时才添加新类型。

添加基本类型

  1. llvm/include/llvm/IR/Type.h:

    为新类型添加枚举;为该类型添加静态 Type*

  2. llvm/lib/IR/Type.cppllvm/lib/CodeGen/ValueTypes.cpp

    添加从 TypeIDType* 的映射;初始化静态 Type*

  3. llvm/include/llvm-c/Core.hllvm/lib/IR/Core.cpp

    添加枚举 LLVMTypeKind 并修改 LLVMTypeKind LLVMGetTypeKind(LLVMTypeRef Ty) 以支持新类型

  4. llvm/lib/AsmParser/LLLexer.cpp:

    添加从文本汇编中解析类型的能力

  5. llvm/lib/AsmParser/LLParser.cpp:

    为该类型添加一个标记

  6. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    修改 void ModuleBitcodeWriter::writeTypeTable() 以序列化你的类型

  7. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    修改 Error BitcodeReader::parseTypeTableBody() 以读取你的数据类型

  8. include/llvm/Bitcode/LLVMBitCodes.h:

    添加枚举 TypeCodes 以支持新类型

添加派生类型

  1. llvm/include/llvm/IR/Type.h:

    为新类型添加枚举;也添加类型的向前声明

  2. llvm/include/llvm/IR/DerivedTypes.h:

    添加新的类来表示层次结构中的新类;向 TypeMap 值类型添加前向声明

  3. llvm/lib/IR/Type.cppllvm/lib/CodeGen/ValueTypes.cpp

    添加对派生类型的支持,特别是 enum TypeIDisget 方法。

  4. llvm/include/llvm-c/Core.hllvm/lib/IR/Core.cpp

    添加枚举 LLVMTypeKind 并修改 LLVMTypeKind LLVMGetTypeKind(LLVMTypeRef Ty) 以支持新类型

  5. llvm/lib/AsmParser/LLLexer.cpp:

    修改 lltok::Kind LLLexer::LexIdentifier() 以添加从文本汇编中解析类型的能力

  6. llvm/lib/Bitcode/Writer/BitcodeWriter.cpp:

    修改 void ModuleBitcodeWriter::writeTypeTable() 以序列化你的类型

  7. llvm/lib/Bitcode/Reader/BitcodeReader.cpp:

    修改 Error BitcodeReader::parseTypeTableBody() 以读取你的数据类型

  8. include/llvm/Bitcode/LLVMBitCodes.h:

    添加枚举 TypeCodes 以支持新类型

  9. llvm/lib/IR/AsmWriter.cpp:

    修改 void TypePrinting::print(Type *Ty, raw_ostream &OS) 以输出新的派生类型