[clang] Add the exclude_from_explicit_instantiation attribute

Summary:
This attribute allows excluding a member of a class template from being part
of an explicit template instantiation of that class template. This also makes
sure that code using such a member will not take for granted that an external
instantiation exists in another translation unit. The attribute was discussed
on cfe-dev at [1] and is primarily motivated by the removal of always_inline
in libc++ to control what's part of the ABI (see links in [1]).

[1]: http://lists.llvm.org/pipermail/cfe-dev/2018-August/059024.html

rdar://problem/43428125

Reviewers: rsmith

Subscribers: dexonsmith, cfe-commits

Differential Revision: https://reviews.llvm.org/D51789

llvm-svn: 343790
This commit is contained in:
Louis Dionne 2018-10-04 15:49:42 +00:00
parent 9b1cc0121c
commit d269579a97
11 changed files with 369 additions and 7 deletions

View File

@ -3042,6 +3042,13 @@ def InternalLinkage : InheritableAttr {
let Documentation = [InternalLinkageDocs];
}
def ExcludeFromExplicitInstantiation : InheritableAttr {
let Spellings = [Clang<"exclude_from_explicit_instantiation">];
let Subjects = SubjectList<[Var, Function, CXXRecord]>;
let Documentation = [ExcludeFromExplicitInstantiationDocs];
let MeaningfulToClassTemplateDefinition = 1;
}
def Reinitializes : InheritableAttr {
let Spellings = [Clang<"reinitializes", 0>];
let Subjects = SubjectList<[NonStaticNonConstCXXMethod], ErrorDiag>;

View File

@ -2975,6 +2975,68 @@ This can be used to contain the ABI of a C++ library by excluding unwanted class
}];
}
def ExcludeFromExplicitInstantiationDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
The ``exclude_from_explicit_instantiation`` attribute opts-out a member of a
class template from being part of explicit template instantiations of that
class template. This means that an explicit instantiation will not instantiate
members of the class template marked with the attribute, but also that code
where an extern template declaration of the enclosing class template is visible
will not take for granted that an external instantiation of the class template
would provide those members (which would otherwise be a link error, since the
explicit instantiation won't provide those members). For example, let's say we
don't want the ``data()`` method to be part of libc++'s ABI. To make sure it
is not exported from the dylib, we give it hidden visibility:
.. code-block:: c++
// in <string>
template <class CharT>
class basic_string {
public:
__attribute__((__visibility__("hidden")))
const value_type* data() const noexcept { ... }
};
template class basic_string<char>;
Since an explicit template instantiation declaration for ``basic_string<char>``
is provided, the compiler is free to assume that ``basic_string<char>::data()``
will be provided by another translation unit, and it is free to produce an
external call to this function. However, since ``data()`` has hidden visibility
and the explicit template instantiation is provided in a shared library (as
opposed to simply another translation unit), ``basic_string<char>::data()``
won't be found and a link error will ensue. This happens because the compiler
assumes that ``basic_string<char>::data()`` is part of the explicit template
instantiation declaration, when it really isn't. To tell the compiler that
``data()`` is not part of the explicit template instantiation declaration, the
``exclude_from_explicit_instantiation`` attribute can be used:
.. code-block:: c++
// in <string>
template <class CharT>
class basic_string {
public:
__attribute__((__visibility__("hidden")))
__attribute__((exclude_from_explicit_instantiation))
const value_type* data() const noexcept { ... }
};
template class basic_string<char>;
Now, the compiler won't assume that ``basic_string<char>::data()`` is provided
externally despite there being an explicit template instantiation declaration:
the compiler will implicitly instantiate ``basic_string<char>::data()`` in the
TUs where it is used.
This attribute can be used on static and non-static member functions of class
templates, static data members of class templates and member classes of class
templates.
}];
}
def DisableTailCallsDocs : Documentation {
let Category = DocCatFunction;
let Content = [{

View File

@ -644,7 +644,8 @@ void Sema::getUndefinedButUsed(
continue;
if (FD->isExternallyVisible() &&
!isExternalWithNoLinkageType(FD) &&
!FD->getMostRecentDecl()->isInlined())
!FD->getMostRecentDecl()->isInlined() &&
!FD->hasAttr<ExcludeFromExplicitInstantiationAttr>())
continue;
if (FD->getBuiltinID())
continue;
@ -654,7 +655,8 @@ void Sema::getUndefinedButUsed(
continue;
if (VD->isExternallyVisible() &&
!isExternalWithNoLinkageType(VD) &&
!VD->getMostRecentDecl()->isInline())
!VD->getMostRecentDecl()->isInline() &&
!VD->hasAttr<ExcludeFromExplicitInstantiationAttr>())
continue;
// Skip VarDecls that lack formal definitions but which we know are in

View File

@ -6512,6 +6512,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case ParsedAttr::AT_InternalLinkage:
handleInternalLinkageAttr(S, D, AL);
break;
case ParsedAttr::AT_ExcludeFromExplicitInstantiation:
handleSimpleAttribute<ExcludeFromExplicitInstantiationAttr>(S, D, AL);
break;
case ParsedAttr::AT_LTOVisibilityPublic:
handleSimpleAttribute<LTOVisibilityPublicAttr>(S, D, AL);
break;

View File

@ -2574,10 +2574,14 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
for (auto *D : Instantiation->decls()) {
bool SuppressNew = false;
if (auto *Function = dyn_cast<FunctionDecl>(D)) {
if (FunctionDecl *Pattern
= Function->getInstantiatedFromMemberFunction()) {
MemberSpecializationInfo *MSInfo
= Function->getMemberSpecializationInfo();
if (FunctionDecl *Pattern =
Function->getInstantiatedFromMemberFunction()) {
if (Function->hasAttr<ExcludeFromExplicitInstantiationAttr>())
continue;
MemberSpecializationInfo *MSInfo =
Function->getMemberSpecializationInfo();
assert(MSInfo && "No member specialization information?");
if (MSInfo->getTemplateSpecializationKind()
== TSK_ExplicitSpecialization)
@ -2618,6 +2622,9 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
continue;
if (Var->isStaticDataMember()) {
if (Var->hasAttr<ExcludeFromExplicitInstantiationAttr>())
continue;
MemberSpecializationInfo *MSInfo = Var->getMemberSpecializationInfo();
assert(MSInfo && "No member specialization information?");
if (MSInfo->getTemplateSpecializationKind()
@ -2649,6 +2656,9 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
}
}
} else if (auto *Record = dyn_cast<CXXRecordDecl>(D)) {
if (Record->hasAttr<ExcludeFromExplicitInstantiationAttr>())
continue;
// Always skip the injected-class-name, along with any
// redeclarations of nested classes, since both would cause us
// to try to instantiate the members of a class twice.

View File

@ -0,0 +1,84 @@
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm -O0 -o - %s | FileCheck %s
// Test that we do not assume that entities marked with the
// exclude_from_explicit_instantiation attribute are instantiated
// in another TU when an extern template instantiation declaration
// is present. We test that by making sure that definitions are
// generated in this TU despite there being an extern template
// instantiation declaration, which is normally not the case.
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
template <class T>
struct Foo {
EXCLUDE_FROM_EXPLICIT_INSTANTIATION inline void non_static_member_function1();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void non_static_member_function2();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static inline void static_member_function1();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function2();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static int static_data_member;
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION member_class1 {
static void static_member_function() { }
};
struct member_class2 {
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function() { }
};
};
template <class T> inline void Foo<T>::non_static_member_function1() { }
template <class T> void Foo<T>::non_static_member_function2() { }
template <class T> inline void Foo<T>::static_member_function1() { }
template <class T> void Foo<T>::static_member_function2() { }
template <class T> int Foo<T>::static_data_member = 0;
extern template struct Foo<int>;
void use() {
Foo<int> f;
// An inline non-static member function marked with the attribute is not
// part of the extern template declaration, so a definition must be emitted
// in this TU.
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE27non_static_member_function1Ev
f.non_static_member_function1();
// A non-inline non-static member function marked with the attribute is
// not part of the extern template declaration, so a definition must be
// emitted in this TU.
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE27non_static_member_function2Ev
f.non_static_member_function2();
// An inline static member function marked with the attribute is not
// part of the extern template declaration, so a definition must be
// emitted in this TU.
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE23static_member_function1Ev
Foo<int>::static_member_function1();
// A non-inline static member function marked with the attribute is not
// part of the extern template declaration, so a definition must be
// emitted in this TU.
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE23static_member_function2Ev
Foo<int>::static_member_function2();
// A static data member marked with the attribute is not part of the
// extern template declaration, so a definition must be emitted in this TU.
// CHECK-DAG: @_ZN3FooIiE18static_data_memberE = linkonce_odr global
int& odr_use = Foo<int>::static_data_member;
// A member class marked with the attribute is not part of the extern
// template declaration (it is not recursively instantiated), so its member
// functions must be emitted in this TU.
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE13member_class122static_member_functionEv
Foo<int>::member_class1::static_member_function();
// A member function marked with the attribute in a member class is not
// part of the extern template declaration of the parent class template, so
// it must be emitted in this TU.
// CHECK-DAG: define linkonce_odr void @_ZN3FooIiE13member_class222static_member_functionEv
Foo<int>::member_class2::static_member_function();
}

View File

@ -2,7 +2,7 @@
// The number of supported attributes should never go down!
// CHECK: #pragma clang attribute supports 128 attributes:
// CHECK: #pragma clang attribute supports 129 attributes:
// CHECK-NEXT: AMDGPUFlatWorkGroupSize (SubjectMatchRule_function)
// CHECK-NEXT: AMDGPUNumSGPR (SubjectMatchRule_function)
// CHECK-NEXT: AMDGPUNumVGPR (SubjectMatchRule_function)
@ -47,6 +47,7 @@
// CHECK-NEXT: DisableTailCalls (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: EnableIf (SubjectMatchRule_function)
// CHECK-NEXT: EnumExtensibility (SubjectMatchRule_enum)
// CHECK-NEXT: ExcludeFromExplicitInstantiation (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record)
// CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable))
// CHECK-NEXT: FlagEnum (SubjectMatchRule_enum)
// CHECK-NEXT: Flatten (SubjectMatchRule_function)

View File

@ -0,0 +1,36 @@
// RUN: %clang_cc1 -fsyntax-only -Wundefined-func-template -Wundefined-var-template -verify %s
// Test that a diagnostic is emitted when an entity marked with the
// exclude_from_explicit_instantiation attribute is not defined in
// the current TU but it is used (and it is hence implicitly
// instantiated).
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
template <class T>
struct Foo {
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void non_static_member_function(); // expected-note{{forward declaration of template entity is here}}
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function(); // expected-note{{forward declaration of template entity is here}}
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static int static_data_member; // expected-note{{forward declaration of template entity is here}}
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION nested {
static int static_member_function(); // expected-note{{forward declaration of template entity is here}}
};
};
extern template struct Foo<int>;
void use() {
Foo<int> foo;
foo.non_static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::non_static_member_function' required here, but no definition is available}}
// expected-note@-1 {{add an explicit instantiation}}
Foo<int>::static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::static_member_function' required here, but no definition is available}}
// expected-note@-1 {{add an explicit instantiation}}
(void)Foo<int>::static_data_member; // expected-warning{{instantiation of variable 'Foo<int>::static_data_member' required here, but no definition is available}}
// expected-note@-1 {{add an explicit instantiation}}
Foo<int>::nested::static_member_function(); // expected-warning{{instantiation of function 'Foo<int>::nested::static_member_function' required here, but no definition is available}}
// expected-note@-1 {{add an explicit instantiation}}
}

View File

@ -0,0 +1,45 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
// Test that explicit instantiations do not instantiate entities
// marked with the exclude_from_explicit_instantiation attribute.
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
template <class T>
struct Foo {
EXCLUDE_FROM_EXPLICIT_INSTANTIATION inline void non_static_member_function1();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void non_static_member_function2();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static inline void static_member_function1();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function2();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static int static_data_member;
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION member_class1 {
static void non_static_member_function() { using Fail = typename T::fail; }
};
struct member_class2 {
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void non_static_member_function() { using Fail = typename T::fail; }
};
};
template <class T>
inline void Foo<T>::non_static_member_function1() { using Fail = typename T::fail; }
template <class T>
void Foo<T>::non_static_member_function2() { using Fail = typename T::fail; }
template <class T>
inline void Foo<T>::static_member_function1() { using Fail = typename T::fail; }
template <class T>
void Foo<T>::static_member_function2() { using Fail = typename T::fail; }
template <class T>
int Foo<T>::static_data_member = T::fail;
// expected-no-diagnostics
template struct Foo<int>;

View File

@ -0,0 +1,69 @@
// RUN: %clang_cc1 -Wno-unused-local-typedef -fsyntax-only -verify %s
// Test that extern instantiation declarations cause members marked with
// the exclude_from_explicit_instantiation attribute to be instantiated in
// the current TU.
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
template <class T>
struct Foo {
EXCLUDE_FROM_EXPLICIT_INSTANTIATION inline void non_static_member_function1();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void non_static_member_function2();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static inline void static_member_function1();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function2();
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static int static_data_member;
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION member_class1 {
static void static_member_function() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
};
struct member_class2 {
EXCLUDE_FROM_EXPLICIT_INSTANTIATION static void static_member_function() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
};
};
template <class T>
inline void Foo<T>::non_static_member_function1() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
template <class T>
void Foo<T>::non_static_member_function2() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
template <class T>
inline void Foo<T>::static_member_function1() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
template <class T>
void Foo<T>::static_member_function2() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
template <class T>
int Foo<T>::static_data_member = T::invalid; // expected-error{{no member named 'invalid' in 'Empty'}}
struct Empty { };
extern template struct Foo<Empty>;
int main() {
Foo<Empty> foo;
foo.non_static_member_function1(); // expected-note{{in instantiation of}}
foo.non_static_member_function2(); // expected-note{{in instantiation of}}
Foo<Empty>::static_member_function1(); // expected-note{{in instantiation of}}
Foo<Empty>::static_member_function2(); // expected-note{{in instantiation of}}
(void)foo.static_data_member; // expected-note{{in instantiation of}}
Foo<Empty>::member_class1::static_member_function(); // expected-note{{in instantiation of}}
Foo<Empty>::member_class2::static_member_function(); // expected-note{{in instantiation of}}
}

View File

@ -0,0 +1,43 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
// Test that we properly merge the exclude_from_explicit_instantiation
// attribute on redeclarations.
#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation))
template <class T>
struct Foo {
// Declaration without the attribute, definition with the attribute.
void func1();
// Declaration with the attribute, definition without the attribute.
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void func2();
// Declaration with the attribute, definition with the attribute.
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void func3();
};
template <class T>
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void Foo<T>::func1() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
template <class T>
void Foo<T>::func2() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
template <class T>
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void Foo<T>::func3() {
using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}}
}
struct Empty { };
extern template struct Foo<Empty>;
int main() {
Foo<Empty> foo;
foo.func1(); // expected-note{{in instantiation of}}
foo.func2(); // expected-note{{in instantiation of}}
foo.func3(); // expected-note{{in instantiation of}}
}