32个版本
0.11.1 | 2024年5月18日 |
---|---|
0.11.0 | 2024年2月24日 |
0.10.0 | 2024年1月26日 |
0.9.0 | 2023年5月31日 |
0.2.1 | 2019年7月25日 |
63 在 编程语言 中排名
63 每月下载量
用于 6 crates
555KB
9K SLoC
llvm-ir:自然Rust数据结构中的LLVM IR
llvm-ir
力求提供一个Rust风格的LLVM IR表示。它基于这样的想法:一个LLVM Instruction
不应该是不可见的数据类型,而是一个具有Add
、Call
和Store
等变体的enum
。同样,像BasicBlock
、Function
和Module
这样的类型应该是尽可能包含信息的Rust结构体。
与inkwell
等其他安全的LLVM绑定不同,llvm-ir
不依赖于持续调用LLVM API的FFI。它只在解析步骤中使用LLVM API,以拉取构建其丰富的LLVM IR表示所需的所有数据。一旦llvm-ir
通过解析LLVM文件(使用出色的llvm-sys
底层LLVM绑定)创建了一个Module
数据结构,它就丢弃了LLVM FFI对象,不再进行任何FFI调用。这使得您可以完全使用安全的Rust来处理生成的LLVM IR。
llvm-ir
用于消费 LLVM IR,但不一定是生产 LLVM IR(尚未)。也就是说,它旨在用于程序分析和相关应用,这些应用需要读取和分析 LLVM IR。未来,也许 llvm-ir
能够将它的 Module
输出到 LLVM 文件中,甚至直接发送到 LLVM 库进行编译。如果您对此感兴趣,欢迎贡献力量!(或者在此期间,您可以查看 inkwell
以获取生成 LLVM IR 的不同安全接口。)但如果您正在寻找一个适用于纯 Rust 的、面向读取的 LLVM IR 的良好表示,那么这正是 llvm-ir
今天可以提供的。
入门
这个软件包位于 crates.io,因此您只需将其作为依赖项添加到您的 Cargo.toml
中,选择与您想要的 LLVM 版本相对应的功能
[dependencies]
llvm-ir = { version = "0.11.1", features = ["llvm-18"] }
目前,支持的 LLVM 版本包括 llvm-9
、llvm-10
、llvm-11
、llvm-12
、llvm-13
、llvm-14
、llvm-15
、llvm-16
、llvm-17
和 llvm-18
。
然后,开始的最简单方法是解析一些现有的 LLVM IR 到这个软件包的数据结构中。为此,您需要 LLVM 位码(*.bc
)或文本格式 IR(*.ll
)文件。如果您目前有 C/C++ 源文件(例如,source.c
),您可以使用 clang
的 -c
和 -emit-llvm
标志生成 *.bc
文件
clang -c -emit-llvm source.c -o source.bc
或者,要编译 Rust 源文件到 LLVM 位码,您可以使用 rustc
的 --emit=llvm-bc
标志。
在任何情况下,一旦您有了位码文件,您就可以使用 llvm-ir
的 Module::from_bc_path
函数
use llvm_ir::Module;
let module = Module::from_bc_path("path/to/my/file.bc")?;
或者如果您有一个文本格式 IR 文件,您可以使用 Module::from_ir_path()
。
您可能还会对 llvm-ir-analysis
软件包感兴趣,它可以计算 llvm-ir
函数的控制流图、支配树等。
文档
llvm-ir
的文档可以在 docs.rs 上找到,或者当然您可以使用 cargo doc --open
生成本地文档。当适用时,文档包括到 LLVM 文档相关部分的链接。
请注意,一些数据结构取决于您选择的 LLVM 版本而略有不同。docs.rs 文档使用 llvm-10
功能生成;对于其他 LLVM 版本,您可以使用以下命令获取适当的文档:cargo doc --features=llvm-<x> --open
其中 <x>
是您使用的 LLVM 版本。
兼容性
从 llvm-ir
0.7.0 开始,LLVM 版本通过 Cargo 功能标志选择。这意味着单个crate版本可以用于任何支持的 LLVM 版本。目前,llvm-ir
支持 9 到 18 的 LLVM 版本,通过功能标志 llvm-9
到 llvm-18
选择。
您应选择与您链接的 LLVM 库版本相对应的 LLVM 版本(即,系统上可用的版本)。较新的 LLVM 应该能够读取较旧 LLVM 产生的位代码,因此您应该能够使用此 crate 解析通过 crate 功能标志选择的 LLVM 版本更旧的位代码,即使是由版本低于 LLVM 9 的 LLVM 产生的位代码。然而,我们并没有对这一点进行广泛的测试。
llvm-ir
在稳定版 Rust 上运行。截至本文撰写时,它需要 Rust 1.65+。
开发和调试
对于开发和调试,除了 *.bc
文件外,您可能还需要 *.ll
文件。
对于 C/C++ 源文件,您可以通过向 clang
传递 -S -emit-llvm
来生成这些文件,而不是传递 -c -emit-llvm
。例如:
clang -S -emit-llvm source.c -o source.ll
对于 Rust 源文件,您可以使用 rustc
的 --emit=llvm-ir
标志。
此外,您可能还需要在生成位代码时向 clang
、clang++
或 rustc
传递 -g
标志。这将生成带有调试信息的 LLVM 位代码,这将确保 Instruction
、Terminator
、GlobalVariable
和 Function
具有有效的 DebugLoc
。 (参见 HasDebugLoc
特性。)请注意,这些 DebugLoc
只在 LLVM 9 及以上版本中可用;LLVM 的旧版本在该 C API 接口中有一个会导致段错误的 bug。
限制
llvm-ir
的数据结构中尚未表示一些 LLVM IR 的功能。
最值得注意的是,llvm-ir
可以恢复调试位置元数据(用于映射回源位置),但并未尝试恢复其他任何调试元数据。包含元数据的LLVM文件仍然可以无问题地解析,但生成的Module
结构将不包含任何元数据,除了调试位置。
由于LLVM C API和Rust llvm-sys
crate中缺少它们的getter,因此llvm-ir
数据结构缺少一些功能。这些包括但不限于
- 各种浮点操作上的“快速数学标志”
- 内联汇编函数的内容
- 关于变长
LandingPad
指令的子句信息 - 关于
BlockAddress
常量表达式的操作数信息 - 关于
TargetExtType
类型的信息 - 与函数关联的“前缀数据”
- 大于64位(且不适合64位)的常量整数值(请参阅#5)
- 从
CallBr
终止符可达的“其他标签”(这是在LLVM 9中引入的) - (LLVM 16及以下版本——在LLVM 17及以后的版本中修复)
Add
、Sub
、Mul
和Shl
上的nsw
和nuw
标志,以及类似的UDiv
、SDiv
、LShr
和AShr
上的exact
标志。C API具有创建指定这些标志值的新指令的功能,但不能查询现有指令上的这些标志值。 - (LLVM 9及以下版本——在LLVM 10及以后的版本中修复)
AtomicRMW
指令的指令码,即Xchg
、Add
、Max
、Min
等。
关于这方面的更多讨论请参阅LLVM错误编号42692。任何有助于填补C API中这些空缺的贡献都将受到高度赞赏!
致谢
llvm-ir
最初受到了llvm-hs-pure
Haskell包的启发。大多数原始版本中的数据结构基本上是将llvm-hs-pure
中的数据结构从Haskell翻译成Rust的结果(进行了一些调整)。
0.7.0版本变更日志
llvm-ir
0.7.0与之前的版本相比有几个相当重大的变化,如下所述。
- 现在通过Cargo功能选择LLVM版本。您必须选择以下功能之一:
llvm-8
、llvm-9
或llvm-10
。以前,我们有一个针对LLVM 10的0.6.x
分支,针对LLVM 9的0.5.x
分支,并且没有正式支持LLVM 8。现在,单个版本支持LLVM 8、9和10。- (注意:此crate的版本号超过0.7.0的版本已添加对后续LLVM版本的支持。例如,0.7.3及以后的版本也支持LLVM 11;0.7.5及以后的版本也支持LLVM 12。crate版本0.11.0删除了对LLVM 8的支持。)
FunctionAttribute
和ParameterAttribute
现在是带有描述性变体的正确枚举,如NoInline
、StackProtect
等。之前,属性是难以解释的不透明数字代码。- 对运行时性能的改进以及特别是内存消耗的改进,尤其是在解析大型 LLVM 模块时。这涉及到对公共接口的许多破坏性更改。
Type
的大多数用户现在拥有一个TypeRef
,而不是直接拥有一个Type
。这包括Operand::LocalOperand
、GlobalVariable
、许多Instruction
的变体、许多Constant
的变体以及一些Type
本身的变体等。请参阅TypeRef
的文档。- 类似地,
Constant
的大多数用户现在拥有一个ConstantRef
,而不是直接拥有一个Constant
。请参阅ConstantRef
的文档。 - 要获取
Typed
对象的类型,现在提供的.get_type()
方法需要额外的参数;大多数用户可能更愿意使用module.type_of()
(或module.types.type_of()
)。 Type::NamedStructType
不再携带对内部类型的弱引用;相反,您可以使用module.types.named_struct_def()
来通过名称查找以获取模块中任何命名结构类型的定义。
- 所需的 Rust 版本从 1.36+ 增加到 1.39+。
- (注意:此存储库版本 0.7.0 之后的版本进一步提高了此要求。有关当前所需的 Rust 版本,请参阅上面的“兼容性”。)