LLVM FAQ

关于许可协议

伊利诺伊大学开源许可证是否真的有资格作为”开源”许可证?

是的,伊利诺伊大学开源许可证通过了开源计划(OSI)的认证

我可以修改LLVM源代码并重新分发修改后的源代码吗?

是的。修改后的源代码分发必须保留版权声明,并遵循LLVM许可证中列出的三个项目符号条件。

我是否可以修改LLVM源代码并基于它重新分发二进制文件或其他工具,而无需重新分发源代码?

是的。这就是为什么我们在比GPL更少限制的许可下分发LLVM,正如上面第一个问题所解释的那样。

关于源代码

LLVM用什么语言编写?

所有LLVM工具和库都是用C++编写的,并广泛使用了STL。

LLVM源代码的可移植性如何?

LLVM源代码应该可以移植到大多数现代类Unix操作系统。大多数代码都是用标准C++编写的,操作系统服务被抽象为支持库。构建和测试LLVM所需的工具已经移植到过多的平台上。

以下方面可能存在一些移植问题:

  • autoconf/makefile构建系统在很大程度上依赖于UNIX shell工具,如Bourne Shell和sed。在没有这些工具的情况下移植到系统(MacOS 9,Plan9)将需要更多的努力。

我使用什么API将值存储到LLVM IR的SSA表示中的一个虚拟寄存器中?

简而言之:你做不到。一旦你了解正在发生的事情,这实际上是一个愚蠢的问题。基本上,在代码中:

%result = add i32 %foo, %bar

%result只是给予一个名字Valueadd指令。换句话说,%result是添加指令。”赋值”没有明确地”存储”任何”虚拟寄存器”=“更像是数学上的平等意识。

更长的解释:为了生成IR的文本表示,必须为每条指令赋予某种名称,以便其他指令可以在文本上引用它。但是,您从C++操作的同构内存中表示没有这样的限制,因为指令可以简单地保持指向Value它们引用的任何其他指针。事实上,虚拟编号的临时代码的名称%1根本没有在内存中表示中明确表示(参见参考资料Value::getName())。

关于源语言

LLVM都支持哪些语言?

LLVM目前通过Clang完全支持C和C++源语言。还有使用LLVM编写了许多其他语言前端,并且在LLVM的项目中提供了不完整的列表 。

我想编写一个自托管的LLVM编译器。我应该如何与LLVM中端优化器和后端代码生成器连接?

您的编译器前端将通过以LLVM中间表示(IR)格式创建模块来与LLVM通信。假设您想用语言本身(而不是C++)编写语言编译器,有三种主要方法可以解决从前端生成LLVM IR的问题:

  1. 使用您的语言的FFI(外部函数接口)调用LLVM库代码。
    • for:最佳跟踪LLVM IR,.ll语法和.bc格式的更改
    • for:允许运行LLVM优化传递而不会产生发出/解析开销
    • for:适应JIT环境
    • 反对:写很多难看的胶水代码
  2. 从编译器的本机语言中发出LLVM程序集。
    • for:非常简单的入门
    • 反对:当连接到中间端时,.ll解析器比bitcode读取器慢
    • 反对:跟踪IR的变化可能更难
  3. 从编译器的本机语言中释放LLVM bitcode。
    • for:可以在连接到中端时使用效率更高的bitcode阅读器
    • 反对:你必须用你的语言重新设计LLVM IR对象模型和bitcode writer
    • 反对:跟踪IR的变化可能更难

如果你选择第一个选项,include/llvm-c中的C绑定应该会有很多帮助,因为大多数语言都支持与C接口。从托管代码调用C的最常见障碍是与垃圾收集器连接。C接口被设计为需要非常少的内存管理,因此在这方面是直截了当的。

对于构建编译器的更高级源语言构造有什么支持?

目前,没有太多。LLVM支持中间表示,该表示对于代码表示很有用,但不支持大多数编译器所需的高级(抽象语法树)表示。没有用于词汇和语义分析的工具。

我不明白这个GetElementPtr指示。救命!

参见经常被误解的GEP指令

关于使用C/C++前端

我可以将C或C ++代码编译为与平台无关的LLVM bitcode吗?

不。C/C++本质上是与平台相关的语言。最明显的例子是预处理器。C代码可移植的一种非常常见的方式是使用预处理器来包含特定于平台的代码。实际上,有关其他平台的信息在预处理后会丢失,因此结果本质上取决于预处理所针对的平台。

另一个例子是sizeofsizeof(long)平台之间的变化很常见。在大多数C前端,sizeof立即扩展为常量,从而硬连接特定于平台的细节。

此外,由于许多平台根据C定义其ABI,并且由于LLVM低于C级,因此前端必须发出特定于平台的IR,以使结果符合平台ABI。

关于演示页面中的代码生成

llvm.global_ctors_GLOBAL__I_a...是什么,我发生的事情?#include<iostream>

如果您在C++编译单元中 #inlcude/,该文件可能会使用std::cin/std::cout/…全局对象。但是,C++不保证不同转换单元中静态对象之间的初始化顺序,因此,如果使用.cpp文件中的静态ctor/dtor std::cout,则在使用之前该对象不一定会自动初始化。

为了使std::cout朋友和朋友在这些场景中正常工作,我们使用的STL声明了一个静态对象,该对象在包含的每个翻译单元中创建<iostream>。此对象具有静态构造函数和析构函数,用于初始化和销毁​​全局iostream对象,然后才能在文件中使用它们。您在.ll文件中看到的代码对应于构造函数和析构函数注册代码。

如果您希望更容易理解编译器在演示页面中生成的LLVM代码,请考虑使用printf()而不是 iostream来打印值。

我的所有代码都去了哪里?

如果您正在使用LLVM演示页面,您可能经常想知道您输入的所有代码发生了什么。请记住,演示脚本是通过LLVM优化器运行代码的,所以如果您的代码实际上没有做任何有用的事情,它可能全部被删除。

为防止这种情况,请确保实际需要代码。例如,如果要计算某个表达式,请从函数返回值,而不是将其保留在局部变量中。如果您真的想约束优化器,可以读取并分配给volatile

undef在我的代码中显示的是什么东西?

undef是LLVM表示未定义的值的方式。如果在使用变量之前未初始化变量,则可以获取这些变量。例如,C函数:int X() { int i; return i; }由于i从来没有一个值初始化它,所以会编译为ret i32 undef

为什么instcombine + simplifycfg将调用不匹配的函数调用为”无法访问”?为什么不让验证者拒绝呢?

这是使用自定义调用约定的前端作者遇到的常见问题:您需要确保在函数和每次调用函数时设置正确的调用约定。例如,这段代码:

define fastcc void @foo() {
ret void
}
define void @bar() {
call void @foo()
ret void
}

将被优化为:

define fastcc void @foo() {
ret void
}
define void @bar() {
unreachable
}

使用 opt [-instcombine] [-simplifycfg]

这经常会让人感到厌烦,因为”所有代码都会消失”。间接调用工作需要在调用者和被调用者上设置调用约定,因此人们常常会问为什么不让验证者拒绝这种事情。

答案是这段代码有不确定的行为,但这并不违法。如果我们把它变成非法的,那么每个可能创建它的转换都必须确保它不会,并且有一些有效的代码可以创建这种构造(在死代码中)。可能导致这种情况发生的各种事情是相当人为的,但我们仍然需要接受它们。这是一个例子:

define fastcc void @foo() {
ret void
}
define internal void @bar(void()* %FP, i1 %cond) {
br i1 %cond, label %T, label %F
T:
call void %FP()
ret void
F:
call fastcc void %FP()
ret void
}
define void @test() {
%X = or i1 false, false
call void @bar(void()* @foo, i1 %X)
ret void
}

在这个例子中,”test”总是传入@foo/ false进入bar,这确保了使用正确的调用conv动态调用它(因此,代码被完美地定义)。如果你通过内联器运行它,你得到这个(显式的”或”是这样的,以便内联器不会死代码消除一堆东西):

define fastcc void @foo() {
ret void
}
define void @test() {
%X = or i1 false, false
br i1 %X, label %T.i, label %F.i
T.i:
call void @foo()
br label %bar.exit
F.i:
call fastcc void @foo()
br label %bar.exit
bar.exit:
ret void
}

在这里,您可以看到内联传递@foo使用错误的调用约定进行了未定义的调用。我们真的不想让内联器必须知道这种事情,所以它需要是有效的代码。在这种情况下,死代码消除可以简单地删除未定义的代码。但是,如果%X是输入参数@test,则内联器会生成:

define fastcc void @foo() {
ret void
}

define void @test(i1 %X) {
br i1 %X, label %T.i, label %F.i
T.i:
call void @foo()
br label %bar.exit
F.i:
call fastcc void @foo()
br label %bar.exit
bar.exit:
ret void
}

关于这一点的有趣之处在于,对于代码的定义%X 必须 是错误的,但是没有多少死代码消除将能够将已损坏的调用删除为无法访问。但是,由于 instcombine/simplifycfg将未定义的调用转换为无法访问,我们最终会在无法访问的情况下得到一个分支:一个分支到无法访问永远不会发生,所以能够生成:-inline -instcombine -simplifycfg

define fastcc void @foo() {
ret void
}
define void @test(i1 %X) {
F.i:
call fastcc void @foo()
ret void
}