编译入门那些事儿(3):不透明指针
发表于 2023/06/30
0
基础知识盘点
(1)IR (Intermediate Representation):LLVM提供的一套编译器系统的中间语言,与具体的语言、指令集、类型系统无关,其中每条指令都是静态单赋值形式(SSA)。
(2)SSA (Static Single Assignment):静态单赋值,变量只被赋值一次,有助于简化变量之间的依赖分析。
(3)Bitcasts (Bitwise Casts):LLVM中一种无操作类型转换,不对数据进行修改,但是可能会导致性能问题[1]。
(4)Data layout:LLVM中一个用于描述数据类型在内存中布局的字符串,指定了数据类型的大小、对齐方式、字节序等信息[2]。
不透明指针简介
LLVM中的类型指针有具体的指针类型信息,这是源于LLVM类型系统最初设计时考虑要支持高层的优化,且指针的类型信息可以用于clang前端校验数据类型是否与IR匹配。
但社区经过长时间的实践经验发现,指针类型信息的携带,弊大于利。早在2015年时便有人提出了不透明指针的概念,统一使用文本ptr表示指针类型,不携带具体指针类型信息[3]。
类型指针的例子:
load i64* %p
不透明指针的例子:
load i64, ptr %p
经过社区多年的整改,在LLVM 15版本不透明指针模式已默认开启,而LLVM 17版本类型指针将不再支持。
类型指针存在的问题[4]
(1)LLVM IR中的指针可以在不同类型间转换,类型指针不一定表示内存中的实际底层类型,不具备具体的语义。
(2)类型指针使IR更加复杂化,当类型不准确时会引入额外并不需要的无操作类型转换IR语句(Bitcasts)。
(3)类型系统初始设计为了支持高层的优化,然而:
a. 内存分析算法通常需要查看结构类型并推导底层内存偏移量;
b. 直接通过SSA形式表示高级语言信息存在局限性,最初提出的一些高层优化演变为TBAA(基于类型的别名分析)。类型指针的类型信息显得多余。
(4)很多操作实际并不关心底层类型,以至于很多这类操作使用任意指针类型 i8*。而这在IR中会引起大量冗余的无操作类型转换。而这些类型转换会:
a. 占用大量内存空间;
b. 占用大量磁盘空间;
c. 消耗额外的编译时间;
d. 增加了分析def-use链的代码复杂度,使得编写相关的优化变得困难。
(5)在前端错误转换指针类型从而导致地址空间信息丢失的问题屡见不鲜,减少指针的无操作类型转换也会减少这类错误转换带来的问题。
(6)整型的有无符号在LLVM早期也遇到过类似的无操作类型转换的问题,当前也已经不再区分有无符号类型,通过flags来区别对待整型。
如何从类型指针重构为不透明指针
针对各种需要获取指针类型以及类型校验的场景,有对应的接口进行查询与判断,具体见下表:
场景 | 相关接口 |
---|---|
loads |
getType() |
stores |
getValueOperand()->getType() |
getelementptr |
getSourceElementType() |
calls |
getFunctionType() |
allocas |
getAllocatedType() |
globals |
getValueType() |
assertions |
isOpaqueOrPointeeTypeEquals() |
constructs |
getWithSamePointeeType() |
checks |
hasSameElementTypeAs() |
byval args |
getParamByValType() |
byref args |
getParamByRefType() |
attributes |
getParamElementType() |
很多时候假定指针类型相同,使用类型指针的场景会做隐式类型转换,而使用不透明指针的场景需要显式地做检查。见如下例子:
define i32 @test(i32* %p) {
store i32 0, i32* %p
%bc = bitcast i32* %p to i64*
%v = load i64, i64* %bc
ret i64 %v
}
define i32 @test(ptr %p) {
store i32 0, ptr %p
%v = load i64, ptr %p
ret i64 %v
}
不透明指针的场景,没有了无操作类型转换(Bitcast),可能会出现例子中load错误类型的情况。此类场景需要显式地检查访问的类型:LI->getType() == SI->getValueOperand()->getType() 。
如何禁用不透明指针
在LLVM 15中,不透明指针默认开启,也提供了一些方式禁用不透明指针。
(1)clang driver:
a. cmake选项:-DCLANG_ENABLE_OPAQUE_POINTERS=OFF
b. 命令行参数:-Xclang -no-opaque-pointers
(2)clang cc1:
-no-opaque-pointers
(3)LTO (Link Time Optimization):
-Wl,-plugin-opt=no-opaque-pointers
(4)Lib (LLVM as library):
LLVMContext::setOpaquePointers(false)
(5)tools (opt/llc 等)
-opaque-pointers=0
总结
类型指针携带的类型信息不是必须的,也不是一定准确的,而携带了类型信息后会引起大量无操作类型转换,导致很多资源损耗以及开发难度增大。
不透明指针不再携带类型信息,所以避免了这些问题。在需要获取或者检查指针类型时,可以通过一些相应的接口进行查询和判断。同时,在彻底重构为不透明指针之前,可以通过一些选项配置先暂时禁用不透明指针。
参考
1. https://llvm.org/doxygen/classllvm_1_1BitCastInst.html2. https://llvm.org/docs/LangRef.html#langref-datalayout
3. https://lists.llvm.org/pipermail/llvm-dev/2015-February/081822.html
4. https://llvm.org/docs/OpaquePointers.html
本页内容