Adding a new attribute: no_extern_template

Hi,

This message is related to solving the issue explained at [1], and indirectly, the usage of always_inline in libc++ as explained at [2].

I’ve been looking to add an attribute (tentatively called no_extern_template) which would prevent member functions in a template from getting available_externally linkage even when there is a matching extern template declaration. In other words, given the following code:

template
struct Foo { void func() { } };
extern template struct Foo;

void use() {
Foo f;
f.func();
}

it is possible for Clang to emit a call to the extern function Foo<int>::func(), because the extern template declaration promises that it exists somewhere else in the program. Clang could also decide to inline the function and generate no extern call, but it does not have to. What I want is an attribute that inhibits generating an external call, i.e. that makes sure Foo<int>::func() is either emitted in this TU despite the extern template declaration, or inlined (in which case a definition is obviously not needed). If the function is emitted, it should be emitted with proper linkage, in this case linkonce_odr (IIUC). The attribute would be applied to the member function that should be opted-out of the extern template declaration, like so:

template
struct Foo { attribute((no_extern_template)) void func() { } };
extern template struct Foo;

I’d like to have some feedback on this idea, specifically:

I think I’m following what you’re asking for: the idea is that an explicit instantiation declaration for the class would not be treated as an explicit instantiation declaration for the members of the class that have the attribute, right? So the desired behavior for:

template struct Foo { // non-polymorphic class
void foo() { }
attribute((no_extern_template)) void bar() { }

void baz() { }

};
extern template struct Foo;

would be exactly the same as the standard behavior of:

template struct Foo {
void foo() { }
void bar() { }

void baz() { }

};
extern void Foo::foo();

extern void Foo::baz();

Yes, this is what I’m asking for.

except that this is an opt-out mechanism whereas the standard mechanism is opt-in. (For polymorphic class types, there’s more differences, as explicit instantiations can also affect where vtables are emitted.)

Given that (I’m guessing) the idea is to more precisely control which functions are part of the libc++ DSO’s ABI, have you considered explicitly listing those functions (that is, following the opt-in strategy) rather than adding an opt-out mechanism? That seems to give more direct control over the ABI surface, albeit at the cost of presumably making the header larger. Is there a reason that approach won’t work or is undesirable?

We’ve talked about it in the thread starting here: http://lists.llvm.org/pipermail/cfe-dev/2018-July/058519.html. I think this is a superior solution because it would create a very clear list of what’s in the ABI. It might be tedious to create (lots of boilerplate declarations), but that’s not a huge concern IMO.

However, the reason why this is not feasible right now is that we lack a way to declare an extern instantiation of a vtable and RTTI, and to explicitly instantiate those. I guess we could either add an extension that allows that and/or try standardizing a feature that allows that. I think standardizing a way of doing this would be useful nonetheless — it would for example remove the need for anchor functions to pick which TU the vtable is emitted in, which is kind of a hack.

GCC for many years had an extension to do this: https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html (see the bottom of the page).

It’s long been on our list of “potentially useful GCC extensions we never got around to implementing”. I’m not sure that’s quite enough to be really useful, though; while that lets you say “instantiate the vtable here”, there doesn’t seem to be a way to say “do not instantiate the vtable here; it was instantiated elsewhere”.

If we can just quietly let that extension die, I think the world would be a better place. The basic idea isn’t bad, but it obviously only uses static and inline because there’s already parser support for them.

If we’re thinking of adding a vendor-specific attribute, why don’t we just add an attribute to mark an explicit instantiation def/decl as only instantiating the class data? Or even just propose that as a feature to the committee? For the latter, I think the obvious spelling would be (with or without parens):

‘extern’? ‘typeid’ ‘(’ type-id ‘)’

Separating this from explicit instantiations would also let it apply to arbitrary other class types, not just templates, which would be nice for micro-optimizing code that uses RTTI but can’t reasonably use key functions (as well as ABIs that don’t provide key-function-like optimizations). If RTTI is disabled, we’d just emit the v-table, and we’d complain if the class wasn’t polymorphic. I guess emitting v-tables in addition to RTTI would prevent someone from using this just to define the RTTI and not the v-table, but… I can’t imagine why someone would want to do that.

John.

I’ve given more thought to Richard’s suggestion. I’m fully on-board with having a way to externally declare and explicitly instantiate typeids and vtables for arbitrary classes (templates or not). I think that’s a great idea that fills a need and I’m willing to pursue a standard proposal that does that.

However, I’m concerned about making libc++’s ability to drop always_inline contingent on such a feature or compiler extension for the following reasons:

  • We have to make sure libc++ still works with GCC. If we wanted to use this hypothetical compiler extension in libc++, we’d have to keep the same annotations as today, but also add the explicit declarations of what’s in the ABI. This increases the complexity of libc++’s visibility system significantly, and that complexity is already too high.

Do you care about supporting more than GCC? If GCC supports the extension Richard linked, then the opt-in strategy can be made portable. But if you care about supporting arbitrary other compilers, that’s tricky.

Based on __config, we seem to support Clang, GCC, MSVC and IBM’s compiler:

#if defined(clang)

define _LIBCPP_COMPILER_CLANG

#elif defined(GNUC)

define _LIBCPP_COMPILER_GCC

#elif defined(_MSC_VER)

define _LIBCPP_COMPILER_MSVC

#elif defined(IBMCPP)

define _LIBCPP_COMPILER_IBM

I don’t know what level of support we have in each of those compilers, since that does not seem to be documented officially.

Okay. So, per Richard’s suggestion, is there anything reasonable we could do in MSVC and IBM if we wanted to switch to opt-in instantiation? Would we just have to have an instantiation of the entire class in the library?

I don’t know what the ABI-stability requirements are on those ports.

Yes, I believe we would basically instantiate the whole class so as to instantiate all the methods we need (and some don’t need), and also the RTTI + vtable when required. I think only those classes that need to export RTTI + vtable would need to instantiate the whole class on compilers that do not support the extension to instantiate only the data. Other classes would export only the members they want to export.

I think this is where we want to drive libc++ in the long term. However, I want to reiterate that doing so is a huge change from the current way we do things, so I would favor a step-by-step approach where we:

  1. solve the problem of always_inline right now with the no_extern_template attribute and minimal risk, and
  2. sign me up for changing libc++ to opt-in instantiation afterwards.

Louis

Hi,

This message is related to solving the issue explained at [1], and indirectly, the usage of always_inline in libc++ as explained at [2].

I’ve been looking to add an attribute (tentatively called no_extern_template) which would prevent member functions in a template from getting available_externally linkage even when there is a matching extern template declaration. In other words, given the following code:

template
struct Foo { void func() { } };
extern template struct Foo;

void use() {
Foo f;
f.func();
}

it is possible for Clang to emit a call to the extern function Foo<int>::func(), because the extern template declaration promises that it exists somewhere else in the program. Clang could also decide to inline the function and generate no extern call, but it does not have to. What I want is an attribute that inhibits generating an external call, i.e. that makes sure Foo<int>::func() is either emitted in this TU despite the extern template declaration, or inlined (in which case a definition is obviously not needed). If the function is emitted, it should be emitted with proper linkage, in this case linkonce_odr (IIUC). The attribute would be applied to the member function that should be opted-out of the extern template declaration, like so:

template
struct Foo { attribute((no_extern_template)) void func() { } };
extern template struct Foo;

I’d like to have some feedback on this idea, specifically:

I think I’m following what you’re asking for: the idea is that an explicit instantiation declaration for the class would not be treated as an explicit instantiation declaration for the members of the class that have the attribute, right? So the desired behavior for:

template struct Foo { // non-polymorphic class
void foo() { }
attribute((no_extern_template)) void bar() { }

void baz() { }

};
extern template struct Foo;

would be exactly the same as the standard behavior of:

template struct Foo {
void foo() { }
void bar() { }

void baz() { }

};
extern void Foo::foo();

extern void Foo::baz();

Yes, this is what I’m asking for.

except that this is an opt-out mechanism whereas the standard mechanism is opt-in. (For polymorphic class types, there’s more differences, as explicit instantiations can also affect where vtables are emitted.)

Given that (I’m guessing) the idea is to more precisely control which functions are part of the libc++ DSO’s ABI, have you considered explicitly listing those functions (that is, following the opt-in strategy) rather than adding an opt-out mechanism? That seems to give more direct control over the ABI surface, albeit at the cost of presumably making the header larger. Is there a reason that approach won’t work or is undesirable?

We’ve talked about it in the thread starting here: http://lists.llvm.org/pipermail/cfe-dev/2018-July/058519.html. I think this is a superior solution because it would create a very clear list of what’s in the ABI. It might be tedious to create (lots of boilerplate declarations), but that’s not a huge concern IMO.

However, the reason why this is not feasible right now is that we lack a way to declare an extern instantiation of a vtable and RTTI, and to explicitly instantiate those. I guess we could either add an extension that allows that and/or try standardizing a feature that allows that. I think standardizing a way of doing this would be useful nonetheless — it would for example remove the need for anchor functions to pick which TU the vtable is emitted in, which is kind of a hack.

GCC for many years had an extension to do this: https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html (see the bottom of the page).

It’s long been on our list of “potentially useful GCC extensions we never got around to implementing”. I’m not sure that’s quite enough to be really useful, though; while that lets you say “instantiate the vtable here”, there doesn’t seem to be a way to say “do not instantiate the vtable here; it was instantiated elsewhere”.

If we can just quietly let that extension die, I think the world would be a better place. The basic idea isn’t bad, but it obviously only uses static and inline because there’s already parser support for them.

If we’re thinking of adding a vendor-specific attribute, why don’t we just add an attribute to mark an explicit instantiation def/decl as only instantiating the class data? Or even just propose that as a feature to the committee? For the latter, I think the obvious spelling would be (with or without parens):

‘extern’? ‘typeid’ ‘(’ type-id ‘)’

Separating this from explicit instantiations would also let it apply to arbitrary other class types, not just templates, which would be nice for micro-optimizing code that uses RTTI but can’t reasonably use key functions (as well as ABIs that don’t provide key-function-like optimizations). If RTTI is disabled, we’d just emit the v-table, and we’d complain if the class wasn’t polymorphic. I guess emitting v-tables in addition to RTTI would prevent someone from using this just to define the RTTI and not the v-table, but… I can’t imagine why someone would want to do that.

John.

I’ve given more thought to Richard’s suggestion. I’m fully on-board with having a way to externally declare and explicitly instantiate typeids and vtables for arbitrary classes (templates or not). I think that’s a great idea that fills a need and I’m willing to pursue a standard proposal that does that.

However, I’m concerned about making libc++’s ability to drop always_inline contingent on such a feature or compiler extension for the following reasons:

  • We have to make sure libc++ still works with GCC. If we wanted to use this hypothetical compiler extension in libc++, we’d have to keep the same annotations as today, but also add the explicit declarations of what’s in the ABI. This increases the complexity of libc++’s visibility system significantly, and that complexity is already too high.

Do you care about supporting more than GCC? If GCC supports the extension Richard linked, then the opt-in strategy can be made portable. But if you care about supporting arbitrary other compilers, that’s tricky.

Based on __config, we seem to support Clang, GCC, MSVC and IBM’s compiler:

#if defined(clang)

define _LIBCPP_COMPILER_CLANG

#elif defined(GNUC)

define _LIBCPP_COMPILER_GCC

#elif defined(_MSC_VER)

define _LIBCPP_COMPILER_MSVC

#elif defined(IBMCPP)

define _LIBCPP_COMPILER_IBM

I don’t know what level of support we have in each of those compilers, since that does not seem to be documented officially.

Okay. So, per Richard’s suggestion, is there anything reasonable we could do in MSVC and IBM if we wanted to switch to opt-in instantiation? Would we just have to have an instantiation of the entire class in the library?

I don’t know what the ABI-stability requirements are on those ports.

Yes, I believe we would basically instantiate the whole class so as to instantiate all the methods we need (and some don’t need), and also the RTTI + vtable when required. I think only those classes that need to export RTTI + vtable would need to instantiate the whole class on compilers that do not support the extension to instantiate only the data. Other classes would export only the members they want to export.

I think this is where we want to drive libc++ in the long term. However, I want to reiterate that doing so is a huge change from the current way we do things, so I would favor a step-by-step approach where we:

  1. solve the problem of always_inline right now with the no_extern_template attribute and minimal risk, and
  2. sign me up for changing libc++ to opt-in instantiation afterwards.

Okay. I’m fine with that approach.

John.