Search code examples
c++g++inline

Why does -fPIC hinder inlining?


I have this foo.cpp:

int add(int a, int b) {
    return a + b;
}

int add_wrapper(int a, int b) {
    return add(a,b);
}

Compiling like so

g++ -c -S -fPIC -O4 -finline-functions  foo.cpp -o foo.s

Gives the (demangled) assembly:

    .globl  add(int, int)
    .type   add(int, int), @function
add(int, int):
.LFB0:
    .cfi_startproc
    endbr64
    leal    (%rdi,%rsi), %eax
    ret
    .cfi_endproc
.LFE0:
    .size   add(int, int), .-add(int, int)
    .p2align 4
    .globl  add_wrapper(int, int)
    .type   add_wrapper(int, int), @function
add_wrapper(int, int):
.LFB1:
    .cfi_startproc
    endbr64
    jmp add(int, int)@PLT
    .cfi_endproc
.LFE1:
    .size   add_wrapper(int, int), .-add_wrapper(int, int)
    .ident  "GCC: (Ubuntu 11.2.0-19ubuntu1) 11.2.0"

Note that add() is not inlined.

However, without -fPIC, I get

    .globl  add(int, int)
    .type   add(int, int), @function
add(int, int):
.LFB0:
    .cfi_startproc
    endbr64
    leal    (%rdi,%rsi), %eax
    ret
    .cfi_endproc
.LFE0:
    .size   add(int, int), .-add(int, int)
    .p2align 4
    .globl  add_wrapper(int, int)
    .type   add_wrapper(int, int), @function
add_wrapper(int, int):
.LFB3:
    .cfi_startproc
    endbr64
    leal    (%rdi,%rsi), %eax
    ret
    .cfi_endproc
.LFE3:
    .size   add_wrapper(int, int), .-add_wrapper(int, int)

With add() inlined.

If I add inline to the declaration of add() and compile with -fPIC, I get

    .globl  add_wrapper(int, int)
    .type   add_wrapper(int, int), @function
add_wrapper(int, int):
.LFB1:
    .cfi_startproc
    endbr64
    leal    (%rdi,%rsi), %eax
    ret
    .cfi_endproc
.LFE1:
    .size   add_wrapper(int, int), .-add_wrapper(int, int)
    .ident  "GCC: (Ubuntu 11.2.0-19ubuntu1) 11.2.0"

With add() omitted entirely.

So it seems that inlining is not disallowed by -fPIC because the compiler will inline if I label the function inline, but I don't understand why the compiler is unwilling to inline automatically with '-fPIC'.

Does anyone know why this is?

Is there a flag that will convince g++ to inline with -fPIC but without labeling functions as inline?

I'm using g++ 11.2, but Compiler Explorer shows that the behavior is consistent across versions: https://godbolt.org/z/Y14edz968.


Solution

  • If you add the always_inline attribute to add, you can see the compiler error when it tries to inline:

    warning: 'always_inline' function might not be inlinable [-Wattributes]
        1 | [[gnu::always_inline]] int add(int a, int b) {
          |                            ^~~
    
    In function 'int add_wrapper(int, int)':
    error: inlining failed in call to 'always_inline' 'int add(int, int)': function body can be overwritten at link time
    note: called from here
        6 |     return add(a,b);
          |            ~~~^~~~~
    

    The add(int, int) symbol might end up being something else (e.g., another library that was in LD_PRELOAD with an int add(int, int) is loaded first, your add_wrapper should call that one).

    You can fix this by making add invisible ([[gnu::visibility("hidden")]]) or not giving it external linkage (with an anonymous namespace or static).

    Or you can make g++ assume this will never happen with the switch -fno-semantic-interposition (which is the default on clang).

    See New option in GCC 5.3: -fno-semantic-interposition for more information about semantic interposition.