为什么人们更喜欢使用LLVM IR,它与GCC IR有何不同?目标依赖是否是一个因素?
我对编译器完全没有经验,在寻找答案的过程中花费了很多时间,但并没有找到任何相关信息。希望能够得到一些见解。
为什么人们更喜欢使用LLVM IR,它与GCC IR有何不同?目标依赖是否是一个因素?
我对编译器完全没有经验,在寻找答案的过程中花费了很多时间,但并没有找到任何相关信息。希望能够得到一些见解。
独立的IR允许LLVM使用IR级别的单元测试,从而可以轻松测试优化/分析的边界情况。通过C/C++片段(如GCC测试套件)实现这一点要困难得多,即使您设法做到了,在未来版本的编译器中生成的IR也很可能会发生显著变化,您的测试旨在覆盖的边界情况将不再被覆盖。
独立的IR使得易于组合来自不同翻译单元的IR,并进行后续(整个程序)优化。虽然这并不完全替代链接时优化(因为它无法处理生产软件中出现的可扩展性问题),但对于较小的程序(例如嵌入式开发或研究项目)通常已足够。
尽管受到学术界的批评, LLVM IR相比于GIMPLE具有更严格的语义,这简化了各种静态分析器(例如IR Verifier)的实现。
LLVM IR由前端(Clang、llgo等)直接生成并在整个中间层中保留。这意味着所有工具、优化和内部API只需要操作单个IR。对于GCC来说并非如此-即使是GIMPLE也有三种不同的变体:
而且,GCC前端通常会生成中间的GENERIC IR而不是GIMPLE。
与GIMPLE相比,LLVM IR通过减少IR消费者需要考虑的情况数量,故意使其更简单。下面我添加了一些例子。
LLVM IR程序中的所有基本块都必须以显式控制流操作码(分支、转到等)结尾。不允许隐式控制流(即落空)。
在LLVM IR中,虚拟寄存器没有内存。堆栈分配由专用的alloca
操作表示。这简化了处理堆栈变量,例如不需要GCC的ADDR_EXPR
的等价项。
与GIMPLE相反,它有大量用于内存引用(INDIRECT_REF、MEM_REF、ARRAY_REF、COMPONENT_REF等)的操作码。LLVM IR仅具有纯负载和存储操作码,并且所有复杂算术都移至专用的结构化索引操作getelementptr中。
LLVM IR为垃圾回收语言提供了专用的伪指令。
虽然C++可能不是最好的编程语言,但它确实允许编写更简单(在许多情况下更具功能性)的系统代码,特别是在C++11之后的变化下(LLVM积极采用新标准)。GCC也采用了C++,但大部分代码库仍以C风格编写。
有太多情况下C++使得代码更简单,因此我只列举一些。
LLVM中运算符的层次结构是通过标准继承和基于模板的自定义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;
tree
类型,通过fat宏接口(DECL_NAME
,TREE_IMAGPART
等)访问。接口仅在运行时验证(仅在GCC配置了--enable-checking
时),不允许静态检查。 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);