人们为什么偏爱LLVM IR,它与GCC IR有何不同?目标依赖性是这里的一个因素吗?

我是编译器的新手,即使经过数小时的寻找答案,也找不到任何相关的内容。任何见解都会有所帮助。

最佳答案

首先,由于这个答案涉及到复杂而敏感的主题,因此我希望免责声明:


我认为您的问题是关于LLVM和GCC的中端IR(因为“ LLVM IR”一词仅适用于中端)。讨论后端IR(LLVM MachineIR和GCC RTL)和相关代码生成工具(LLVM Tablegen和GCC Machine Description)之间的差异是一个有趣且重要的话题,但答案会大很多倍。
我省去了基于库的LLVM设计与GCC的整体设计,因为它与IR本身是独立的(尽管相关)。
我喜欢在GCC和LLVM上进行黑客攻击,但我不敢一一列举。 LLVM之所以如此,是因为人们可以从GCC在2000年代出错的事情中学习(从那时起,情况有了很大的改善)。
我很高兴改善此答案,因此,如果您认为某些内容不正确或缺少,请发表评论。


最重要的事实是LLVM IR和GCC IR(称为GIMPLE)在内核上并没有什么不同-都是基本块的标准控制流程图,每个块都是2个输入,1个输出指令的线性序列(所谓的“三地址代码”)已转换为SSA form。自1990年代以来,大多数生产编译器一直在使用这种设计。

LLVM IR的主要优点是它与编译器实现的绑定更紧密,定义更正式,并且具有更好的C ++ API。这使得处理,转换和分析更加容易,这使得它成为当今编译器和其他相关工具的首选。

我将在下面的子章节中详细介绍LLVM IR的优势。

独立红外

LLVM IR最初设计为可在除编译器本身之外的所有工具之间完全重用。 original intent用于多级优化:因此,IR将在运行时由提前编译器,链接时优化器和JIT编译器进行优化。这没有解决,但是可重用性还有其他重要意义,最明显的是,它允许轻松集成其他类型的工具(静态分析仪,仪器等)。

除了编译器之外,GCC社区从未希望启用任何工具(Richard Stallman拒绝尝试使IR更可重用,以防止第三方商业工具重用GCC的前端)。因此,从未将GIMPLE(GCC的IR)视为实现细节,尤其是它没有提供已编译程序的完整描述(例如,它缺少程序的调用图,类型定义,堆栈偏移和别名信息)。

灵活的管道

可重用性和使IR成为独立实体的想法在LLVM中产生了重要的设计结果:编译过程可以以任何顺序运行,以防止复杂的过程间依赖(必须通过分析过程来明确所有依赖),并使实验更加容易与编译管道,例如


每次通过后都要进行严格的IR验证检查
bisecting pipeline查找导致编译器崩溃的最小传递子集
通行证的模糊顺序


更好的单元测试支持

独立的IR允许LLVM使用IR级别的单元测试,从而可以轻松测试优化/分析案例。通过C / C ++片段(如在GCC测试套件中)很难做到这一点,即使在您进行管理时,生成的IR也很可能在以后的编译器版本中发生重大变化,并且测试所针对的特殊情况将不再被掩盖。

简单的链接时间优化

独立IR通过后续(整个程序)优化实现简单的combination of IR from separate translation units。这不能完全替代链接时间优化(因为它不能解决生产软件中出现的可伸缩性问题),但通常对于较小的程序(例如在嵌入式开发或研究项目中)已经足够了。

红外清晰度更高

尽管criticized by academia,但LLVM IR的semanticsGIMPLE严格得多。这简化了各种静态分析器的实现,例如IR Verifier

无中间IR

LLVM IR由前端(Clang,llgo等)直接生成,并在整个中端保留。这意味着所有工具,优化和内部API仅需在单个IR上运行。对于GCC,情况并非如此-甚至GIMPLE也具有三个不同的变体:


高GIMPLE(包括词汇范围,高级控制流构造等)
低SSA前的GIMPLE
最终的SSA GIMPLE和GCC前端通常会生成中间GENERIC IR而不是GIMPLE。


红外更简单

与GIMPLE相比,通过减少IR消费者需要考虑的案例数量,故意使LLVM IR更加简单。我在下面添加了几个示例。

显式控制流

LLVM IR程序中的所有基本块都必须以显式控制流操作码(分支,goto等)结尾。不允许隐式控制流(即掉线)。

显式堆栈分配

在LLVM IR中,虚拟寄存器没有内存。堆栈分配由专用的alloca操作表示。这简化了使用堆栈变量(例如不需要等价于GCC的ADDR_EXPR

显式索引操作

与GIMPLE相反,GIMPLE具有大量用于内存引用的操作码(INDIRECT_REF,MEM_REF,ARRAY_REF,COMPONENT_REF等),而LLVM IR仅具有简单的加载和存储操作码,所有复杂的算法都移至专用的结构化索引操作码getelementptr

垃圾收集支持

LLVM IR为垃圾收集的语言提供了dedicated pseudo-instructions

高级实现语言

尽管C ++可能不是最好的编程语言,但它绝对可以编写更简单(在许多情况下,更多功能)的系统代码,
特别是在C ++ 11之后的版本中(LLVM积极采用新标准)。继LLVM之后,GCC也采用了C ++,但是大多数代码库仍是用C风格编写的。

C ++启用了更简单的代码的实例太多了,因此我仅举几个例子。

显式等级

LLVM中的运算符层次结构是通过标准继承和template-based custom RTTI实现的。另一方面,GCC通过旧式继承(通过聚合)实现相同功能

// Base class which all operators aggregate
struct GTY(()) tree_base {
  ENUM_BITFIELD(tree_code) code : 16;

  unsigned side_effects_flag : 1;
  unsigned constant_flag : 1;
  unsigned addressable_flag : 1;

  ...  // Many more fields
};

// Typed operators add type to base data
struct GTY(()) tree_typed {
  struct tree_base base;
  tree type;
};

// Constants add integer value to typed node data
struct GTY(()) tree_int_cst {
  struct tree_typed typed;
  HOST_WIDE_INT val[1];
};

// Complex numbers add real and imaginary components to typed data
struct GTY(()) tree_complex {
  struct tree_typed typed;
  tree real;
  tree imag;
};

// Many more operators follow
...



和标记的联合范例:

union GTY ((ptr_alias (union lang_tree_node),
            desc ("tree_node_structure (&%h)"), variable_size)) tree_node {
  struct tree_base GTY ((tag ("TS_BASE"))) base;
  struct tree_typed GTY ((tag ("TS_TYPED"))) typed;
  struct tree_int_cst GTY ((tag ("TS_INT_CST"))) int_cst;
  struct tree_complex GTY ((tag ("TS_COMPLEX"))) complex;


所有GCC操作员API都使用基本的tree类型,可通过fat宏接口(DECL_NAMETREE_IMAGPART等)访问该类型。仅在运行时验证接口(并且仅当GCC配置了--enable-checking时),并且不允许静态检查。

更简洁的API

LLVM通常为优化器中的模式匹配IR提供更简单的API。例如在GCC中检查指令是否为常数加法

  if (gimple_assign_p (stmt)
      && gimple_assign_rhs_code (stmt) == PLUS_EXPR
      && TREE_CODE (gimple_assign_rhs2 (stmt)) == INTEGER_CST)
    {
      ...


在LLVM中:

  if (auto BO = dyn_cast<BinaryOperator>(V))
  if (BO->getOpcode() == Instruction::Add
      && isa<ConstantInt>(BO->getOperand(1))
    {


任意精度算法

由于C ++对重载的支持,LLVM可以对所有计算使用任意精度的整数,而GCC仍使用物理整数(HOST_WIDE_INT类型,在32位主机上为32位):

  if (!tree_fits_shwi_p (arg1))
    return false;

  *exponent = tree_to_shwi (arg1);


如示例中所示,这可能导致错过优化。

几年前,GCC获得了APInt的等价物,但是大多数代码库仍使用HOST_WIDE_INT

关于gcc - GCC IR与LLVM IR有何不同?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40799696/

10-13 00:06