GCC可以从最终输出中消除哪种死代码?

wbrvyc0a  于 2023-08-03  发布在  其他
关注(0)|答案(3)|浏览(122)

我一直被告知编译器足够聪明,可以消除死代码。我写的大部分代码在编译时都有很多已知的信息,但代码必须以最通用的形式编写。我不知道任何程序集,因此无法检查生成的程序集。什么样的代码可以在最终的可执行文件中被有效地消除?
几个例子,但不限于

f(bool b){
 if(b){
  //some code
 }else{
  //some code
 }
}
f(true);
//////////////////////////
template<bool b>
f(){
 if(b){
  //some code
 }else{
  //some code
 }
}
f<true>();
///////////////////////////

字符串
如果f的定义在其他目标代码中,而被调用的f(true)在main中,该怎么办?链接时间优化是否能有效地消除死代码?什么是编码风格/编译器选项/技巧,以促进死代码消除?

5tmbdcev

5tmbdcev1#

通常情况下,如果你在the -O flag上编译,下面的标志会被打开:

-fauto-inc-dec 
      -fcompare-elim 
      -fcprop-registers 
      -fdce  
      [...]

字符串
-fdce是Dead Code Elimination的缩写。我建议你编译你的二进制文件有和没有(即。通过显式地关闭)这个选项来确保你的二进制文件是否像你希望的那样优化。
阅读关于编译器的different passes

  • SSA积极的死代码消除。由“-fssa-dce”选项打开。这一遍执行被认为是不必要的代码的消除,因为它对程序没有外部可见的影响。它以线性时间运行。

至于帮助链接器消除死代码,请通过this presentation。两个主要要点是:
用-ffunction-sections -fdata-sections编译你的模块--它没有任何缺点!

  • 这包括静态库,而不仅仅是二进制文件-使您的库的用户可以从更有效的死代码删除中受益。
  • 用--gc-sections链接你的二进制文件,除非你必须链接到讨厌的第三方静态库,它使用了魔术节。

您可能还想看看this GCC bug(看看可能会错过哪些优化机会以及原因)。

xurqigkl

xurqigkl2#

功能消除

另一种类型的死代码消除,GCC可以删除整个未使用的符号(函数或变量),可以通过以下方式实现:

-fdata-sections -ffunction-sections -Wl,--gc-sections

字符串
如在:How to remove unused C/C++ symbols with GCC and ld?
这些标志在各种GCC -O级别(-O 1、-O2等)by default中未启用。
还如以下所示:GCC LTO是否执行跨文件死代码消除?如果一个函数由于LTO而被内联,那么它就不会被计入编译单元被放置在最终输出中。

让我们尝试一些函数内DCE

让我们来测试一下GCC能做什么或不能做什么。测试LLM模型很无聊。
main.c

int main(int argc, char **argv) {
    if (0) {
        argc += 0x33;
    }
    return argc;
}


编译和反汇编:

gcc -O0 main.c
gdb -batch -ex 'disassemble/rs main' a.out


输出量:

0x0000000000001129 <+0>:     f3 0f 1e fa             endbr64
   0x000000000000112d <+4>:     55                      push   %rbp
   0x000000000000112e <+5>:     48 89 e5                mov    %rsp,%rbp
   0x0000000000001131 <+8>:     89 7d fc                mov    %edi,-0x4(%rbp)
   0x0000000000001134 <+11>:    48 89 75 f0             mov    %rsi,-0x10(%rbp)
   0x0000000000001138 <+15>:    8b 45 fc                mov    -0x4(%rbp),%eax
   0x000000000000113b <+18>:    5d                      pop    %rbp
   0x000000000000113c <+19>:    c3                      ret


是的,不见了。
其他一些:
未在-O3中删除,它不知道argc必须为正:

int main(int argc, char **argv) {
    if (argc >= 0) {
        argc += 0x33;
    }
    return argc;
}


0x 44在-O3中被删除,但在-O4中没有,所以它可以执行if/else算法:

int main(int argc, char **argv) {
    if (argc >= 0) {
        argc += 0x33;
    } else {
        if (argc >= 0) {
            argc += 0x44;
        }
    }
    return argc;
}


0x 33在-O3中被删除,因此它可以执行范围和+

int main(int argc, char **argv) {
    if (argc < 10000) {
        if (argc + 1 > 10000) {
            argc += 0x33;
        }
    }
    return argc;
}


0x 33没有被删除,它不能推理sqrt

#include <math.h>

int main(int argc, char **argv) {
    if (argc < 10000) {
        if (sqrt(argc) > 100) {
            argc += 0x33;
        }
    }
    return argc;
}

hgb9j2n6

hgb9j2n63#

当我在这样的if表达式中使用模板参数常量时,dce(死代码消除)编译器(Linux上的GCC 4.8.1)标志没有帮助,O2,O3优化也没有帮助。我不得不使用模板专门化 Package 器:

template<bool b>
f();

template<>
f<true>(){
  //some code on true condition
}

template<>
f<false>(){
  //some code on false condition
}

字符串
也可以使用宏来避免编译未使用的代码分支,但这取决于编译器(它是在代码中还是在预编译阶段处理宏):

template<bool b>
f(){
 #if b
  //some code
 #elif
  //some code
 #endif  // b
}

相关问题