【问题标题】:How is GCC IR different from LLVM IR?GCC IR 与 LLVM IR 有何不同?
【发布时间】:2017-04-09 13:00:57
【问题描述】:

为什么人们更喜欢 LLVM IR,它与 GCC IR 究竟有何不同?目标依赖是这里的一个因素吗?

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

【问题讨论】:

  • 最大的实际区别是 LLVM 有一个可用的模块化 API。对于 GCC,开发人员尽其所能确保这样的 API 不会被暴露。这就是为什么如果你想插入 IR,LLVM 是一个首选的解决方案。

标签: gcc compilation compiler-construction llvm-ir


【解决方案1】:

首先,由于这个答案涉及复杂和敏感的话题,我想几句免责声明

  • 我认为您的问题是关于 LLVM 和 GCC 的中端 IR(因为术语“LLVM IR”仅适用于中端)。讨论后端 IR(LLVM MachineIR 和 GCC RTL)和相关代码生成工具(LLVM Tablegen 和 GCC 机器描述)的差异是一个有趣且重要的话题,但会使答案扩大数倍。
  • 我省略了 LLVM 的基于库的设计与 GCC 的整体设计,因为这与 IR 本身是分开的(尽管相关)。
  • 我喜欢在 GCC 和 LLVM 上进行黑客攻击,而且我不会把其中一个放在首位。 LLVM 之所以如此,是因为人们可以从 GCC 在 2000 年代所犯的错误中吸取教训(从那时起,这些错误已经得到了显着改进)。
  • 我很高兴改进此答案,因此如果您认为某些内容不准确或缺失,请发布 cmets。

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

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

我将在下面的子章节中详细介绍 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++ sn-ps(如在 GCC 测试套件中)实现这一点要困难得多,即使您管理,生成的 IR 很可能会在编译器的未来版本中发生显着变化,并且您的测试打算用于的极端情况将不再被覆盖。

简单的链接时优化

独立 IR 可轻松实现 combination of IR from separate translation units 并进行后续(整个程序)优化。这不是链接时优化的完全替代品(因为它不处理生产软件中出现的可扩展性问题),但通常对于较小的程序(例如嵌入式开发或研究项目)来说已经足够了。

更严格的 IR 定义

尽管criticized by academia,LLVM IR 与GIMPLE 相比,semantics 严格得多。这简化了各种静态分析器的实现,例如IR Verifier.

无中间 IR

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

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

更简单的红外

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

显式控制流

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

显式堆栈分配

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

显式索引操作

与 GIMPLE 有大量内存引用操作码(INDIRECT_REF、MEM_REF、ARRAY_REF、COMPONENT_REF 等)相反,LLVM IR 只有普通的加载和存储操作码,所有复杂的算术都移到专用的结构化索引操作码 @987654329 @。

垃圾回收支持

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 宏接口(@98​​7654338@、TREE_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 已经获得了与 APInts 等效的版本,但大部分代码库仍然使用 HOST_WIDE_INT

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多