Linker symbol resolution differences with LTO.

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Linker symbol resolution differences with LTO.

shankarke@gmail.com
Why is symbol resolution different when you use LTO versus a non LTO build.

Test :-

cat > 1.c << \!
int bar() {
  return foo();
}

__attribute__((weak)) int foo() {
  return 0;
}
!

cat > 2.c << \!
int baz() {
  return foo();
}

__attribute__((weak)) int foo() {
  return 1;
}
!


gcc -c 1.c  -ffunction-sections -flto
gcc -c 2.c -ffunction-sections
# Linker picks the definition of foo from 2.o
ld 1.o 2.o -y foo
2.o: definition of foo


gcc -c 1.c  -ffunction-sections
gcc -c 2.c -ffunction-sections
# Linker picks the definition of foo from 1.o
ld 1.o 2.o -y foo
1.o: definition of foo

This will confuse a lot of users moving to LTO builds.

Changing symbol visibility complicates things even more.

Can this be fixed (or) is there a user specific way of controlling behavior
?




Reply | Threaded
Open this post in threaded view
|

Re: Linker symbol resolution differences with LTO.

Cary Coutant-3
> gcc -c 1.c  -ffunction-sections -flto
> gcc -c 2.c -ffunction-sections
> # Linker picks the definition of foo from 2.o
> ld 1.o 2.o -y foo
> 2.o: definition of foo

If you run "nm" on 1.o, I think you'll find that there are no symbols
defined other than a couple of __gnu_lto_ symbols. This indicates that
you're compiling to "slim" objects, which contain *only* intermediate
code. Without the LTO plugin, the linker will find nothing to link in
1.o. If you want the ability to link LTO-compiled objects without
using the LTO plugin, you'll want to use the -ffat-lto-objects option
the object file will contain not only the intermediate code (for use
with the linker plugin), but also regular object code that can be
linked without a plugin. In that case, linking with plain ld, without
the plugin, will pick the right definition of foo, but you will not
get any link-time optimization -- you'll get the same output you would
have gotten without any -flto options at all.

You're not really using LTO (*Link-time* optimization) unless you use
"gcc -flto" for the link step as well. The gcc driver will pass the
necessary options to the linker to have it call the LTO plugin, which
will re-invoke the compiler to translate the intermediate code into
final object code at link time. Furthermore, if you only compile one
file with -flto, you won't really be gaining much at all, since the
link-time optimization will not be able to perform any cross-module
optimization.

-cary
Reply | Threaded
Open this post in threaded view
|

Re: Linker symbol resolution differences with LTO.

Cary Coutant-3
I suspect this is because you compiled 2.c without LTO. When the LTO
plugin invokes the compiler to generate code for 1.c, it sees that
there is already a definition for foo, so it doesn't bother to
generate another definition. If you compile both 1.c and 2.c with LTO,
I think it will give you the result you expect.

I think the compiler is within its rights in doing this, but I could
be wrong. (For C++, with the ODR, I'm pretty sure; for C, I'm not so
sure.)

-cary


On Mon, Dec 7, 2015 at 6:09 PM, Shankar Easwaran <[hidden email]> wrote:

> Thanks for the quick reply, Cary.
>
> I was only trying to reduce the testcase where I am seeing odd behavior with
> LTO.
>
> I am changing the testcase a bit, but still get different symbol resolution
> behavior with LTO and non LTO builds.
>
> cat > 1.c << \!
> #include <stdio.h>
>
> int bar() {
>   return 0;
> }
>
> __attribute__((weak)) int foo() {
>   printf("good\n");
>   return 0;
> }
> !
>
> cat > 2.c << \!
> #include <stdio.h>
> int main() {
>   return foo() + bar();
> }
>
> __attribute__((weak)) int foo() {
>   printf("bad\n");
>   return 1;
> }
> !
>
>
> gcc -c 1.c  -ffunction-sections -flto
> gcc -c 2.c -ffunction-sections
> gcc -flto 1.o 2.o -Wl,-y,foo -o 1
> # with LTO, prints bad
> ./1
> gcc 1.c 2.c -o 2 -Wl,-y,foo
> # without LTO, prints good
> ./2
>
> I am expecting the test to produce good in both cases.
>
> Shankar Easwaran
>
>
> On Mon, Dec 7, 2015 at 7:20 PM, Cary Coutant <[hidden email]> wrote:
>>
>> > gcc -c 1.c  -ffunction-sections -flto
>> > gcc -c 2.c -ffunction-sections
>> > # Linker picks the definition of foo from 2.o
>> > ld 1.o 2.o -y foo
>> > 2.o: definition of foo
>>
>> If you run "nm" on 1.o, I think you'll find that there are no symbols
>> defined other than a couple of __gnu_lto_ symbols. This indicates that
>> you're compiling to "slim" objects, which contain *only* intermediate
>> code. Without the LTO plugin, the linker will find nothing to link in
>> 1.o. If you want the ability to link LTO-compiled objects without
>> using the LTO plugin, you'll want to use the -ffat-lto-objects option
>> the object file will contain not only the intermediate code (for use
>> with the linker plugin), but also regular object code that can be
>> linked without a plugin. In that case, linking with plain ld, without
>> the plugin, will pick the right definition of foo, but you will not
>> get any link-time optimization -- you'll get the same output you would
>> have gotten without any -flto options at all.
>>
>> You're not really using LTO (*Link-time* optimization) unless you use
>> "gcc -flto" for the link step as well. The gcc driver will pass the
>> necessary options to the linker to have it call the LTO plugin, which
>> will re-invoke the compiler to translate the intermediate code into
>> final object code at link time. Furthermore, if you only compile one
>> file with -flto, you won't really be gaining much at all, since the
>> link-time optimization will not be able to perform any cross-module
>> optimization.
>>
>> -cary
>
>