假设我有一个函数
void do_something() {
//....
#ifdef FEATURE_X
feature_x();
#endif
//....
}
我可以毫无问题地编译和运行它;如果我想要这个功能,我可以传递-D FEATURE_X
并且它可以工作。
但是,如果我想将do_something
放入另一个文件中(并且不必在每次我决定更改选项时重新编译该文件)。 如果它在同一文件中,我假设
const int FEATURE_X=0;
void do_something() {
//....
if(FEATURE_X) {
feature_x();
}
//....
}
将正确使用死代码消除,消除调用。 如果我把它放在另一个文件中,没有 LTO,
extern const int FEATURE_X;
void do_something() {
//....
if(FEATURE_X) {
feature_x();
}
//....
}
它不会删除代码(它无法知道)。 那么,启用链接时间优化后,编译器是否可以在链接时检测FEATURE_X
的值,确定代码是否被使用,并在适当时将其删除?
GCC 确实会跨模块删除无法访问的函数,但它将无法确定代码在上一个测试用例中是否失效,因为 FEATURE_X 的常量值确定为时已晚。
如果您将使用 -D 方式或将const int FEATURE_X=0;
放入每个模块中,那么是的,代码将被消除。
显示 LTO 导致死代码消除的示例
测试设置:
notmain.c
int notmain(int i) {
return i + 1;
}
int notmain2(int i) {
return i + 2;
}
主.c
int notmain(int);
int main(int argc, char **argv) {
return notmain(argc);
}
无 LTO 的对照实验
在没有 LTO 的情况下编译和反汇编:
gcc -O3 -c notmain.c
gcc -O3 notmain.o main.c
objdump -d a.out
输出包含:
0000000000001040 <main>:
1040: f3 0f 1e fa endbr64
1044: e9 f7 00 00 00 jmp 1140 <notmain>
1049: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001140 <notmain>:
1140: f3 0f 1e fa endbr64
1144: 8d 47 01 lea 0x1(%rdi),%eax
1147: c3 ret
1148: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
114f: 00
0000000000001150 <notmain2>:
1150: f3 0f 1e fa endbr64
1154: 8d 47 02 lea 0x2(%rdi),%eax
1157: c3 ret
所以没有删除无用的notmain2
。
我们还可以查看对象大小:
size a.out
其中输出:
text data bss dec hex filename
1304 544 8 1856 740 a.out
此外,作为奖励,我们注意到函数调用没有内联:
0000000000001040 <main>:
1040: f3 0f 1e fa endbr64
1044: e9 f7 00 00 00 jmp 1140 <notmain>
1049: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
观察 LTO 执行 DCE
gcc -c -flto -O3 notmain.c
gcc -flto -O3 notmain.o main.c
objdump -d a.out
输出不包含notmain
符号,不包含notmain2
符号。所有内容都完全内联到main
中,在单个指令中将 1 添加到第一个参数 rdi,并将其放入返回寄存器 eax:
0000000000001040 <main>:
1040: f3 0f 1e fa endbr64
1044: 8d 47 01 lea 0x1(%rdi),%eax
1047: c3 ret
1048: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
内联还提到:链接时间优化和内联
美。检查尺寸:
size a.out
输出:
text data bss dec hex filename
1217 544 8 1769 6e9 a.out
我们看到,由于内联和死代码消除,文本大小根据需要减小。
LTO 执行 DCE,即使内联未发生
在上面的示例中,不清楚函数 DCE 消除是否仅在涉及内联时才发生。因此,让我们用以下方法进行测试:
int __attribute__ ((noinline)) notmain(int i) {
return i + 1;
}
编译和反汇编:
gcc -c -flto -O3 notmain.c
gcc -flto -O3 notmain.o main.c
objdump -d a.out
输出包含:
0000000000001040 <main>:
1040: f3 0f 1e fa endbr64
1044: e9 f7 00 00 00 jmp 1140 <notmain>
1049: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001140 <notmain>:
1140: 8d 47 01 lea 0x1(%rdi),%eax
1143: c3 ret
没有notmain2
.因此,即使没有删除无用notmain2
notmain
也被删除了。
使用-O0
编译notmain.c
时不会发生函数删除
我不明白为什么:为什么 GCC 在使用 -O0 编译目标文件时不使用 LTO 进行功能死代码消除?
在 Ubuntu 23.04 amd64、GCC 12.2.0 上测试。