arrow_back_ios Back to List

Avoid code duplication in the g++ ABI

The C++ ABI implemented by gcc and other compilers performs some often unnecessary code duplication on constructors/destructors leading to code bloat which is undesirable especially on platforms with low memory footprints such as the Cell SPUs.

Code bloat caused by not-in-charge method definitions

The following example code defines a base and a derived struct. Each of them declares a constructor which is defined outside the struct definition to force the compiler to generate the definition of the function in the object file.

struct base
{   base();

struct derived:base
{   derived();

derived::derived(){} //just 2 function definitions, right? Wrong!

We would expect that only two function definitions end up in the object file but the compiler generates four function definitions as can be seen in the following assembly generated by gcc (g++-4 version 4.3.4. on Cygwin)

00000000 <__ZN4baseC2Ev>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   5d                      pop    %ebp
   4:   c3                      ret
   5:   90                      nop

00000006 <__ZN4baseC1Ev>:
   6:   55                      push   %ebp
   7:   89 e5                   mov    %esp,%ebp
   9:   5d                      pop    %ebp
   a:   c3                      ret
   b:   90                      nop

0000000c <__ZN7derivedC2Ev>:
   c:   55                      push   %ebp
   d:   89 e5                   mov    %esp,%ebp
   f:   83 ec 04                sub    $0x4,%esp
  12:   8b 45 08                mov    0x8(%ebp),%eax
  15:   89 04 24                mov    %eax,(%esp)
  18:   e8 e3 ff ff ff          call   0 <__ZN4baseC2Ev>
  1d:   c9                      leave
  1e:   c3                      ret
  1f:   90                      nop

00000020 <__ZN7derivedC1Ev>:
  20:   55                      push   %ebp
  21:   89 e5                   mov    %esp,%ebp
  23:   83 ec 04                sub    $0x4,%esp
  26:   8b 45 08                mov    0x8(%ebp),%eax
  29:   89 04 24                mov    %eax,(%esp)
  2c:   e8 cf ff ff ff          call   0 <__ZN4baseC2Ev>
  31:   c9                      leave
  32:   c3                      ret
  33:   90                      nop

The reason for the four functions (with the mangled function names: __ZN4baseC1Ev, __ZN4baseC2Ev, __ZN7derivedC1Ev, __ZN7derivedC2Ev) in the assembly instead of the expected two functions is that the gcc ABI duplicates constructor and destructor definitions to create not-in-charge constructors (C2 in the mangled function name) and destructors (D2 in mangled name, not shown in this example). These not-in-charge constructors/destructors are called only by derived constructors/destructors and perform the construction/destruction of the object without constructing/destructing virtual bases. However, for struct definitions that do not have virtual bases (like in the code above) the in-charge constructor (for example __ZN4baseC1Ev) is identical to the not-in-charge constructor (__ZN4baseC2Ev). This can lead to significant code bloat especially if the constructors/destructor definitions are large and emitted in the object file (i.e. not inlined).

Avoiding the code bloat

Codeplay's compilers (including Offload™) provide a command line option -noredundantgccmethods to disable this code duplication and generate not-in-charge functions (C2 or D2 in the mangled name) only where needed (i.e. where virtual bases are present). Using that option the Codeplay Vectorc compiler generates only two function definitions (in-charge functions with C1 in the mangled name) on the example above. Using that option calls to not-in-charge functions are replaced by calls to in-charge functions, for example in the following unoptimised assembly the in-charge constructor __ZN7derivedC1Ev now calls the in-charge base constructor __ZN4baseC1Ev.

00000000 <__ZN4baseC1Ev>:
   0:   c3                      ret
   1:   8d 84 20 00 00 00 00    lea    0x0(%eax,%eiz,1),%eax
   8:   8d 84 20 00 00 00 00    lea    0x0(%eax,%eiz,1),%eax
   f:   90                      nop

00000010 <__ZN7derivedC1Ev>:
  10:   8b 44 24 04             mov    0x4(%esp),%eax
  14:   50                      push   %eax
  15:   e8 e6 ff ff ff          call   0 <__ZN4baseC1Ev>
  1a:   83 c4 04                add    $0x4,%esp
  1d:   c3                      ret

The compiler suppresses the definition of not-in-charge methods on those structures (that don't have virtual bases) and calls the in-charge methods instead to initialise the base. Since using that option the not-in-charge are not emitted in the object file, any usage of those functions from code compiled without that option (including from gcc- compiled code), may result in a link error.