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

介绍和警告

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

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

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

在您投入大量精力进行非平凡的扩展之前,请在列表上询问您想要做的事情是否可以使用现有的基础设施完成,或者是否有人已经在做这件事。这样做将为您节省大量时间和精力。

添加新的内部函数

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

  1. llvm/docs/LangRef.html:

    记录内部函数。确定它是否是代码生成器特定的,以及有哪些限制。与其他人讨论它,以确保这是一个好主意。

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

    为您的内部函数添加一个条目。描述其内存访问特性以进行优化(这控制着它是否会被 DCE,CSE 等优化掉)。如果任何参数需要是立即数,则必须使用 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)。在其他情况下,添加新节点是为了允许许多目标执行通用任务(在浮点和整数表示之间转换)或在单个节点中捕获更复杂的行为(rotate)。

  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,以教 Legalizer 如何在已拆分为高半部分和低半部分的值上执行新节点表示的操作。此 case 将用于在 32 位目标上支持具有 64 位运算数的节点。

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

    如果您的节点可以与其自身或其他现有节点以类似于窥孔的方式组合,请为其添加一个 visit 函数,并从中调用该函数。对于您可以执行的简单组合,有几个很好的例子;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.td 中的 rotl 模式。

  10. TODO:记录复杂模式。

  11. llvm/test/CodeGen/*:

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

添加新的指令

警告

添加指令会更改位代码格式,并且需要花费一些精力来保持与以前版本的兼容性。仅在绝对必要时才添加指令。

  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,以及如何从位代码中解析它

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

    为您的指令添加一个 case,以及如何从位代码中解析它

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

    为您的指令如何打印到汇编中添加一个 case

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

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

  10. 测试您的指令

  11. llvm/lib/Target/*:

    为您的指令添加到代码生成器的支持,或添加一个 lowering Pass。

  12. llvm/test/*:

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

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

添加新的类型

警告

添加新类型会更改位代码格式,并且会破坏与当前现有 LLVM 安装的兼容性。仅在绝对必要时才添加新类型。

添加基本类型

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

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

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

    添加从 TypeID => Type* 的映射;初始化静态 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) 以输出新的派生类型