What kind of dead code can GCC eliminate from the final output?
Asked Answered
S

3

20

I have always been told that compiler is sufficient smart to eliminate dead code. Much of the code that I am writing has a lot of information known at compile time but the code has to be written in most generic form. I don't know any assembly so I cannot examine the generated assembly. What kind of code that can be effectively eliminated in the final executable?

Few examples but not limited to

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>();
///////////////////////////

What if definition of f is in other objective code and the the called f(true) is in main. Will link time optimisation effectively eliminate the dead code? What is the coding style/compiler option/trick to facilitate dead code elimination?

Serranid answered 30/5, 2012 at 2:56 Comment(3)
You should read How can I know which parts in the code are never used?.Mcmaster
@JesseGood This is helpful! thanks. But I dont need to know which code is dead, I just want to make sure they are not compiled into the final executable. In fact I know on purpose which part of the code is dead by design when I write the caller code.Serranid
Look in the GCC documentation. Are all possible cases of dead code enumerated there? If not, you are unlikely to find them enumerated anywhere.Oxbow
S
28

Typically, if you're compiling with the -O flag on the following flags are turned on:

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

-fdce stands for Dead Code Elimination. I'd suggest you compile your binaries with and without (i.e. by turning off explicitly) this option to make sure if your binaries are as optimized as you'd like them to be.

Read about the different passes of the compiler:

  • SSA Aggressive Dead Code Elimination. Turned on by the `-fssa-dce' option. This pass performs elimination of code considered unnecessary because it has no externally visible effects on the program. It operates in linear time.

As for helping the linker with dead code elimination go through this presentation. Two major takeaways being:

Compile your modules with -ffunction-sections -fdata-sections – there are no downsides to it!

  • This includes static libraries, not just binaries – make it possible for users of your library to benefit from more efficient dead code removal.
  • Link your binaries with --gc-sections, unless you have to link against nasty third-party static library which uses magic sections.

You may also want to take a look at this GCC bug (to see what chances of optimization may be missed and why).

Serow answered 30/5, 2012 at 3:13 Comment(7)
Question: In the docs, they use the acronym "RTL", what does that stand for?Mcmaster
@*:RTL = Register Transfer Language is a Lisp-inspired low-level intermediate language (one of the three used by GCC) used (along with another called GIMPLE) for optimization purposes.Serow
FYI, if your code or a library needs magic sections, then the linker script should be marking those sections as KEEP. This kind of thing happens a lot in embedded development (which often needs linker scripts to define memory layout anyway), as things like exception vector tables are never called or referenced by main but need to be there for the platform. BTW, if you're building embedded code without -ffunction-sections, -fdata-sections, and --gc-sections`, you're only hurting yourself.Chino
Also, since --gc-sections and similar flags are relatively new and not widely supported, library writers (especially C standard library writers) would generally stick to one library function per source file, achieving a similar effect. See glibc as an example.Chino
@*:How to exactly turn off "-fdce" explicitly when -O is turn on?Incardination
@Incardination -fno-[the-named-optimisation-you-dont-want]. this is documented and can be found fairly easily.Scintillant
The presentation you quoted doesn't align with what the current GCC man page says about the -f*-sections options -- "Only use these options when there are significant benefits from doing so. When you specify these options, the assembler and linker create larger object and executable files and are also slower. They prevent optimizations by the compiler and assembler using relative locations inside a translation unit ..."Centavo
T
3

Function elimination

Another type of dead code elimination that GCC can do the removal of entire unused symbols (functions or variables) which can be achieved with:

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

as mentioned at: How to remove unused C/C++ symbols with GCC and ld?

These flags are not enabled in the various GCC -O levels (-O1, -O2, etc) by default.

Also as shown at: Does GCC LTO perform cross-file dead code elimination? if a function would be inlined due to LTO, then it doesn't count towards making a compilation unit get placed in the final output.

Let's try some intra-function DCE

Let's have some fun testing what GCC can do or not. Testing LLM models is boring.

main.c

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

Compile and disassemble:

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

Output:

   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

Yup, it's gone.

Some others:

Not removed in -O3, it does not know that argc has to be positive:

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

The 0x44 is removed in -O3 but not -O4, so it can do if/else arithmetic:

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

The 0x33 is removed in -O3 so it can do ranges and +:

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

0x33 is not removed, it can't reason about sqrt:

#include <math.h>

int main(int argc, char **argv) {
    if (argc < 10000) {
        if (sqrt(argc) > 100) {
            argc += 0x33;
        }
    }
    return argc;
}
Tko answered 11/11, 2015 at 9:59 Comment(0)
C
0

When I used template parameter constant in such if expression then dce (Dead Code Elimination) compiler (GCC 4.8.1 on Linux) flags did not helped and O2, O3 optimization also did not helped. I had to use template specialization wrapper:

template<bool b>
f();

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

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

Also macros can be used to avoid compilation of the unused code branch, but it depends on the compiler (whether it processed macros as they occur in the code or on precompiling stage):

template<bool b>
f(){
 #if b
  //some code
 #elif
  //some code
 #endif  // b
}
Connor answered 14/10, 2013 at 16:21 Comment(1)
The #if b is independent of the similarly named bool b template parameter.Solomonsolon

© 2022 - 2024 — McMap. All rights reserved.