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 版本,请参阅上面的“兼容性”。)