LTO, shared libraries and extern class templates

Hadrien Grasland grasland@lal.in2p3.fr
Thu Oct 11 16:12:00 GMT 2018


Hi Jonathan,

Thanks for clarifying that explicit class template instantiations should 
work. Trying to produce a minimal reproducer was a very interesting 
exercise. As it turns out, the problematic situation which I am 
experiencing does not occur with explicit template instantiations per 
se, but only when those are combined with inheritance in a certain way.

Let us consider a toy library composed of this header...

----

#pragma once

template <typename T>
class Superclass {
   public:
     void print(const T& value) const;
};

template <typename T>
class Subclass : public Superclass<T> {
   public:
     void print_sub(const T& value) const;
};

extern template class Subclass<int>;

----

...and this source file...

----

#include <iostream>
#include "lib.hpp"

template <typename T>
void Superclass<T>::print(const T& value) const {
     std::cout << "The value is " << value << std::endl;
}

template <typename T>
void Subclass<T>::print_sub(const T& value) const {
     this->print(value);
}

template class Subclass<int>;

----

On a regular build of this library, both Superclass<int>::print() and 
Subclass<int>::print_sub() are exposed as weak symbols and can be linked 
against:

----

00000000000011da  w    F .text  0000000000000051              Superclass<int>::print(int const&) const
00000000000011b4  w    F .text  0000000000000026              Subclass<int>::print_sub(int const&) const

----

On an LTO build from GCC 8.2, however, Superclass<int>::print() becomes 
a local symbol which cannot be linked against:

----

000000000000116c l     F .text  0000000000000050              Superclass<int>::print(int const&) const
0000000000001146 g     F .text  0000000000000025              Subclass<int>::print_sub(int const&) const

----

If Superclass<int> is also explicitly instantiated, both symbols will be 
global in the LTO build too. However, having to explicitly instantiate 
the whole class hierarchy like this could quickly get tedious.

I would spontaneously expect an explicit instantiation of Subclass to 
feature all the code that one needs in order to use Subclass, including 
the Superclass code. Is the behavior above expected ?

Cheers,
Hadrien


Le 11/10/2018 à 15:44, Jonathan Wakely a écrit :
> On Thu, 11 Oct 2018 at 14:01, Hadrien Grasland <grasland@lal.in2p3.fr> wrote:
>> Hi everyone,
>>
>> This is a follow-up to my previous e-mail about linkers, now that I
>> understand the problem that I'm dealing with well enough to describe it :)
>>
>> I'm trying to enable LTO in a large, heavily templated C++ codebase,
>> whose developers made a lot of effort to improve compilation efficiency.
>> To this end...
>>
>>    * Significant effort is expended in separating class declarations and
>>      definitions in regular OO code
>>    * C++11's "extern template" and explicit template instantiations are
>>      extensively used to reduce template-induced duplicate work.
>>    * Shared libraries are also used, sometimes in a cascading fashion
>>      where an executable depends on library A, which depends on another
>>      library B
>>
>> When I enable LTO, I notice that many weak function template symbols
>> turn into local symbols, and are thus not available anymore to clients
>> of a shared library. This is a somewhat expected side-effect of LTO,
>> since AFAIK one of its design goals is to discard unused symbols.
>>
>> My problem is that this weak symbol pruning process is in my case a
>> little bit too agressive. For example, in the "cascading" scenario
>> above, I end up in a situation where library A fails to link because it
>> cannot find the function symbols associated with "extern template class"
>> declarations in the headers of library B. Note that this only happens
>> with extern template classes: extern template functions keep working as
>> expected.
> That suggests a bug in library B. The explicit instantiation
> declaration (the "extern template" bit) needs to be matched by an
> explicit instantiation *definition*. That definition should not be
> weak, and so should not be discarded.
>
> It sounds like you've been using "extern template" to suppress
> implicit instantiations, and then assuming that some other library
> will happen to provide the definition via some other implicit
> instantiation. With LTO the other implicit instantiations are being
> pruned, and the assumption fails.
>
> If you're doing it properly, using explicit instantiation definitions
> to provide the needed symbols, then they should not get dropped by the
> LTO linker. If they do, I think that's a bug in GCC and/or the linker.
>
>> I can work around this by providing library A with the definitions of
>> the relevant templates instead of the declarations, falling back to a
>> classic duplicate instantiation model, but I am curious if there is a
>> better way. Can I somehow structure or annotate the C++ code so that the
>> linker knows that the explicit template instantiations of library B are
>> used by its clients (like library A) and must be kept around as weak or
>> global symbols of the output shared library?
> Does library B really have explicit instantiation definitions? When I
> create a shared library using LTO any explicit instantiations are
> turned into GLOBAL symbols, not WEAK, and are not removed.
>
> It would help if you can create a minimal example showing the problem
> you describe.



More information about the Gcc-help mailing list