GCC LTO是否执行跨文件死代码消除?

jm81lzqq  于 2023-08-06  发布在  其他
关注(0)|答案(2)|浏览(125)

说我有一个功能

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的值,确定是否使用了该代码,并在适当的情况下删除它?

yxyvkwin

yxyvkwin1#

GCC确实可以跨模块删除不可访问的函数,但它将无法在您的最后一个测试用例中确定代码已经死亡,因为FEATURE_X的常量值将被确定得太晚。
如果你将使用-D方式或将你的const int FEATURE_X=0;放入每个模块中,那么是的,代码将被消除。

ou6hu8tu

ou6hu8tu2#

LTO导致死代码消除的示例

测试设置:
notmain.c

int notmain(int i) {
    return i + 1;
}

int notmain2(int i) {
    return i + 2;
}

字符串
main.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


输出不包含notmainnotmain2符号。所有内容都完全内联到main中,它在一条指令中将第一个参数rdi加1,并将其放入返回寄存器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)


Inlining还提到:链接时间优化和内联
漂亮。检查尺寸:

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。因此,即使notmain没有被删除,无用的notmain2也被删除了。

使用-O0编译notmain.c时不会删除函数

我不明白为什么:Why GCC does not do function dead code elimination with LTO when compiling the object file with -O0?
在Ubuntu 23.04 amd64,GCC 12.2.0上测试。

相关问题