- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
人们为什么偏爱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的semantics比GIMPLE严格得多。这简化了各种静态分析器的实现,例如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;
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)
{
...
if (auto BO = dyn_cast<BinaryOperator>(V))
if (BO->getOpcode() == Instruction::Add
&& isa<ConstantInt>(BO->getOperand(1))
{
HOST_WIDE_INT
类型,在32位主机上为32位):
if (!tree_fits_shwi_p (arg1))
return false;
*exponent = tree_to_shwi (arg1);
APInt
的等价物,但是大多数代码库仍使用
HOST_WIDE_INT
。
关于gcc - GCC IR与LLVM IR有何不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40799696/
我正在尝试在 Conda 环境中编译一些代码,在那里我 之前安装的编译包gcc_linux-64 . 然而,即使在停用和重新激活环境之后,gcc还在/usr/bin/gcc . 我该怎么做才能让 Co
关闭。这个问题是opinion-based .它目前不接受答案。 想要改进这个问题? 更新问题,以便 editing this post 可以用事实和引用来回答它. 关闭 7 年前。 Improve
这其实是两个问题: 1 - 在我的 debian amd64 系统上,我似乎无法构建与 gmp/mpfr/mpc 动态链接的交叉 GCC。即使我删除 --disable-shared,它也总是静态链接
研究ELF格式的结果,可以看到目标文件中有一个符号对应每个函数,对应的符号表项的值为st_size,表示大小的功能。 问题是,即使我更改了目标文件中特定函数的 st_size 并链接了它,但可执行文件
海湾合作委员会的 documentation for #line directives说他们是这样的: #line "myfile.cpp" 123 但是当我用 g++ 5.1 检查输出时,它们实际上
我正在使用 as 和 gcc 来汇编和创建 ARM 汇编程序的可执行文件,正如 this 所推荐的那样教程,如下: 给定一个汇编源文件,program.s,我运行: as -o program.o p
long long x; double n; x=long long(n); 这不起作用。什么是正确的方法? 最佳答案 显而易见的: x = (long long) n; 关于gcc - 转换为长长
我想知道用于 gcc 的原子内置函数的头文件是什么? 我想使用这 2 个函数为我当前创建的线程库实现互斥锁。 bool __sync_bool_compare_and_swap (type *ptr,
它出现在 another question :gcc调用的程序和部件是什么? (特别是在编译 C 或 C++ 时)以便有人可以设计一些拦截和更改流程的方案以用于各种自定义编码目的? 最佳答案 编译器二
可能吗?我想使用 gcc喜欢 assembler并在将其编译为 ubuntu 上的可执行文件后。 我尝试过这个: gcc a.asm -o out.o 来自 out.o文件编译成.out可执行文件。
我写了一个简单的 C 程序 test.c : #include #include int add(int a, int b); int main() { int i=5,j=10;
即。所以如果你使用任何八进制文字,它会给你一个警告。 微软编译器的同样问题。 如果没有,是否有任何其他工具可以检测八进制文字。 (vim 似乎有一个很酷的技巧,它突出了第一个领先的将不同的颜色归零,但
我在旧线程中搜索。但没有找到任何线程回答我的问题。 gcc 是否像 vc++ 一样支持函数级链接? 如果是,我应该提供什么选项来链接目标文件和库? 最佳答案 看起来 gcc 不直接支持函数级链接。您可
也许标题并没有把问题说得那么准确:我知道当我运行 gcc foo.c 时,GCC 会调用其他为它完成所有工作的子程序,从而生成主 gcc 程序只是一个界面。但这究竟是如何完成的呢? 它是否使用syst
我听说最近版本的 gcc 非常擅长将通过函数指针的调用转换为直接调用。但是,我在网上或快速浏览 gcc 的源代码上找不到任何关于它的信息。有谁知道这是否真的是真的,如果是这样,它使用什么算法来做到这一
gcc/g++ 链接器选项“-Map”生成的“.map”文件用于什么? 以及如何阅读它们? 最佳答案 我建议为您投入生产的任何软件生成一个映射文件并保留一份副本。 它可用于破译崩溃报告。根据系统的不同
gcc信息文件在有关x86-64特定标志的部分中说 其他事情: There is no `-march=generic' option because `-march' ind
我想知道 gcc 链接器选项(例如:-Wl,options)是否可以更改编译后的可执行文件中的汇编指令,因为如果您使用某些 gcc 优化选项会发生这种情况? 当您比较编译后的二进制文件(例如比较签名)
是否有GCC编译指示会停止,暂停或中止编译过程? 我正在使用gcc 4.1,但也希望在gcc 3.x版本上也可以使用该编译指示。 最佳答案 您可能需要#error: edd@ron:/tmp$ g++
当我使用gcc编译C程序时我通常使用 -g 将一些调试信息放入 elf 文件中这样 gdb 就可以在需要时帮助我。 但是,我注意到有些程序使用 -ggdb,因为它应该使调试信息对 gdb 更加友好。
我是一名优秀的程序员,十分优秀!