ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

llvm常见问题 (FAQ)

2021-07-04 07:31:36  阅读:207  来源: 互联网

标签:常见问题 源代码 代码 FAQ ret llvm foo void LLVM


llvm常见问题 (FAQ)

License

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

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

源代码

LLVM 是用什么语言编写的?

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

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

源语言

支持哪些源语言?

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

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

不明白GetElementPtr指令。帮助!

使用 C 和 C++ 前端

可以将 C 或 C++ 代码编译为独立于平台的 LLVM bitcode位码吗?

关于demo页面生成代码的问题

当#include <iostream>发生的事情?llvm.global_ctors以及_GLOBAL__I_a...是什么 ?

出现在代码中的这个“undef ”是什么?

“Go“代码在哪里使用?

为什么 instcombine +simplifiedcfg对不匹配的函数的调用变成“无法访问”?为什么不让验证者拒绝?

许可证license

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

是的。修改后的源代码分发必须保留版权声明并遵循Apache License v2.0 with LLVM Exceptions 中列出的条件。

 

可以修改 LLVM 源代码,重新分发二进制文件或工具,而无需重新分发源代码吗?

是的。在比 GPL 限制更少的许可下分发 LLVM,如上面的第一个问题所述。

 

源代码

LLVM 是用什么语言编写的?

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

 

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

LLVM 源代码应该可以移植到大多数modern Unix-like操作系统。LLVM 对 Windows 系统也有很好的支持。大多数代码是用标准 C++ 编写的,操作系统服务抽象支持库。构建和测试 LLVM 所需的工具已移植到大平台。

 

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

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

 

%result = add i32 %foo, %bar

%result只是给予一个add 指令Value。换句话说,%result 就是添加指令。“赋值”没有明确地将任何东西“存储”到任何“虚拟寄存器”;“ =”更像是数学意义上的相等。

 

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

 

源语言

支持哪些源语言?

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

 

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

编译器前端将通过以 LLVM 中间表示 (IR) 格式,创建模块来与 LLVM 通信。用语言(不是 C++)编写编译器,有 3 种主要方法可以从前端生成 LLVM IR:

 

使用语言的 FFI(外部函数接口)调用 LLVM 库代码。

for:最佳跟踪对 LLVM IR、.ll 语法和 .bc 格式的更改

for:启用运行 LLVM 优化过程,无需发出/解析开销

for:很好地适应 JIT 上下文

against:要写很多丑陋的胶水代码

从编译器的本地语言发出 LLVM 汇编程序集。

for:非常容易上手

against:当连接到中间端时,.ll 解析器比bitcode位码读取器慢

against:跟踪 IR 的变化可能更难

从编译器的本地语言发出 LLVM 位码。

for:在与中端接口时,可以使用更高效的bitcode位码reader

against:必须用语言重新设计 LLVM IR 对象模型和位码编写器

against:跟踪 IR 的变化可能更难

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

 

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

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

 

不明白GetElementPtr指令。帮助!

请参阅 The Often Misunderstood GEP Instruction

 

使用 C 和 C++ 前端

可以将 C 或 C++ 代码编译为独立于平台的 LLVM 位码吗?

不,C 和 C++ 本质上是依赖于平台的语言。最明显的例子是预处理器。使 C 代码具有可移植性的非常常见方法,使用预处理器来包含特定于平台的代码。实际上,预处理后,平台的信息会丢失,结果本质上取决于预处理所针对的平台。

 

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

 

此外,由于许多平台根据 C 定义它们的 ABI,并且由于 LLVM 比 C 级别低,因此前端当前必须发出platform-specific的 IR,以使结果符合平台 ABI。

 

关于demo页面生成代码的问题

当#include <iostream>发生的事情,llvm.global_ctors以及_GLOBAL__I_a...是什么?

如果#include的<iostream>header换成C ++翻译单元,该文件可能会使用std::cin/ std::cout/ ...全局对象。但是,C++ 不保证不同翻译单元中静态对象之间的初始化顺序,静态std::cout,例如,如果使用了 .cpp 文件中的静态 ctor/dtor ,该对象不一定会在使用自动初始化。

 

为了让std::cout和friends 在这些场景中正常工作,使用的 STL 声明了一个静态对象,该对象在包含<iostream>。该对象具有静态构造函数和析构函数,用于在全局 iostream 对象,可能在文件中使用之前初始化和销毁。在.ll文件中看到的代码,对应于构造函数和析构函数的注册代码。

 

如果希望更容易理解演示页面中编译器生成的 LLVM 代码,请参考使用printf()代替 iostreams 来打印信息。

 

所有的代码都去哪儿了?

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

 

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

 

出现在代码中的这个“undef ”是什么?

undef是表示未定义值的 LLVM 方式。如果在使用变量之前未初始化变量,则可以获得这些。例如,C 函数:

 

int X() { int i; return i; }

编译为“ret i32 undef”,因为“i ”从未为其指定值。

为什么 instcombine +simplifiedcfg 将对调用约定不匹配的函数的调用变成“无法访问”?为什么不让验证者拒绝?

这是使用自定义调用约定的前端作者遇到的一个常见问题:

需要确保在函数和每次调用函数时,都设置正确的调用约定。例如,这段代码:

 

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”。这通常会bites people难受,因为“他们所有的代码都消失了”。间接调用需要在调用者和被调用者上设置调用约定,所以人们经常问为什么不让验证者拒绝这种事情。

 

答案是这段代码有未定义的行为,但并不违法。如果将其设为非法,那么每个可能会创建的转换都必须确保不会发生,并且存在可以创建此类构造的有效代码(在死代码中)。可能导致这种情况发生的事情是相当contrived的,但仍然需要接受。下面是一个例子:

 

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/ falseinto 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 必须为 false,但没有多少死代码能够删除无法访问的损坏调用。由于 instcombine/simplifycfg将 undefined 调用变为 unreachable,最终得到了一个条件为 unreachable 的分支:

unreachable 的分支永远不会发生,所以“-inline -instcombine -simplifycfg”能够产生:

 

define fastcc void @foo() {

   ret void

}

define void @test(i1 %X) {

F.i:

   call fastcc void @foo()

   ret void

}

 

参考链接:Frequently Asked Questions (FAQ

标签:常见问题,源代码,代码,FAQ,ret,llvm,foo,void,LLVM
来源: https://www.cnblogs.com/wujianming-110117/p/14968187.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有