Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -3042,6 +3042,13 @@ 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>; Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -2975,6 +2975,68 @@ }]; } +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 + template + class basic_string { + public: + __attribute__((__visibility__("hidden"))) + const value_type* data() const noexcept { ... } + }; + + template class basic_string; + +Since an explicit template instantiation declaration for ``basic_string`` +is provided, the compiler is free to assume that ``basic_string::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::data()`` +won't be found and a link error will ensue. This happens because the compiler +assumes that ``basic_string::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 + template + class basic_string { + public: + __attribute__((__visibility__("hidden"))) + __attribute__((exclude_from_explicit_instantiation)) + const value_type* data() const noexcept { ... } + }; + + template class basic_string; + +Now, the compiler won't assume that ``basic_string::data()`` is provided +externally despite there being an explicit template instantiation declaration: +the compiler will implicitly instantiate ``basic_string::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 = [{ Index: lib/Sema/Sema.cpp =================================================================== --- lib/Sema/Sema.cpp +++ lib/Sema/Sema.cpp @@ -644,7 +644,8 @@ continue; if (FD->isExternallyVisible() && !isExternalWithNoLinkageType(FD) && - !FD->getMostRecentDecl()->isInlined()) + !FD->getMostRecentDecl()->isInlined() && + !FD->hasAttr()) continue; if (FD->getBuiltinID()) continue; @@ -654,7 +655,8 @@ continue; if (VD->isExternallyVisible() && !isExternalWithNoLinkageType(VD) && - !VD->getMostRecentDecl()->isInline()) + !VD->getMostRecentDecl()->isInline() && + !VD->hasAttr()) continue; // Skip VarDecls that lack formal definitions but which we know are in Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -6512,6 +6512,9 @@ case ParsedAttr::AT_InternalLinkage: handleInternalLinkageAttr(S, D, AL); break; + case ParsedAttr::AT_ExcludeFromExplicitInstantiation: + handleSimpleAttribute(S, D, AL); + break; case ParsedAttr::AT_LTOVisibilityPublic: handleSimpleAttribute(S, D, AL); break; Index: lib/Sema/SemaTemplateInstantiate.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiate.cpp +++ lib/Sema/SemaTemplateInstantiate.cpp @@ -2574,10 +2574,14 @@ for (auto *D : Instantiation->decls()) { bool SuppressNew = false; if (auto *Function = dyn_cast(D)) { - if (FunctionDecl *Pattern - = Function->getInstantiatedFromMemberFunction()) { - MemberSpecializationInfo *MSInfo - = Function->getMemberSpecializationInfo(); + if (FunctionDecl *Pattern = + Function->getInstantiatedFromMemberFunction()) { + + if (Function->hasAttr()) + continue; + + MemberSpecializationInfo *MSInfo = + Function->getMemberSpecializationInfo(); assert(MSInfo && "No member specialization information?"); if (MSInfo->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) @@ -2618,6 +2622,9 @@ continue; if (Var->isStaticDataMember()) { + if (Var->hasAttr()) + continue; + MemberSpecializationInfo *MSInfo = Var->getMemberSpecializationInfo(); assert(MSInfo && "No member specialization information?"); if (MSInfo->getTemplateSpecializationKind() @@ -2649,6 +2656,9 @@ } } } else if (auto *Record = dyn_cast(D)) { + if (Record->hasAttr()) + 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. Index: test/CodeGenCXX/attr-exclude_from_explicit_instantiation.dont_assume_extern_instantiation.cpp =================================================================== --- test/CodeGenCXX/attr-exclude_from_explicit_instantiation.dont_assume_extern_instantiation.cpp +++ test/CodeGenCXX/attr-exclude_from_explicit_instantiation.dont_assume_extern_instantiation.cpp @@ -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 +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 inline void Foo::non_static_member_function1() { } +template void Foo::non_static_member_function2() { } + +template inline void Foo::static_member_function1() { } +template void Foo::static_member_function2() { } + +template int Foo::static_data_member = 0; + +extern template struct Foo; + +void use() { + Foo 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::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::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::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::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::member_class2::static_member_function(); +} Index: test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- test/Misc/pragma-attribute-supported-attributes-list.test +++ test/Misc/pragma-attribute-supported-attributes-list.test @@ -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) Index: test/SemaCXX/attr-exclude_from_explicit_instantiation.diagnose_on_undefined_entity.cpp =================================================================== --- test/SemaCXX/attr-exclude_from_explicit_instantiation.diagnose_on_undefined_entity.cpp +++ test/SemaCXX/attr-exclude_from_explicit_instantiation.diagnose_on_undefined_entity.cpp @@ -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 +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; + +void use() { + Foo foo; + + foo.non_static_member_function(); // expected-warning{{instantiation of function 'Foo::non_static_member_function' required here, but no definition is available}} + // expected-note@-1 {{add an explicit instantiation}} + + Foo::static_member_function(); // expected-warning{{instantiation of function 'Foo::static_member_function' required here, but no definition is available}} + // expected-note@-1 {{add an explicit instantiation}} + + (void)Foo::static_data_member; // expected-warning{{instantiation of variable 'Foo::static_data_member' required here, but no definition is available}} + // expected-note@-1 {{add an explicit instantiation}} + + Foo::nested::static_member_function(); // expected-warning{{instantiation of function 'Foo::nested::static_member_function' required here, but no definition is available}} + // expected-note@-1 {{add an explicit instantiation}} +} Index: test/SemaCXX/attr-exclude_from_explicit_instantiation.explicit_instantiation.cpp =================================================================== --- test/SemaCXX/attr-exclude_from_explicit_instantiation.explicit_instantiation.cpp +++ test/SemaCXX/attr-exclude_from_explicit_instantiation.explicit_instantiation.cpp @@ -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 +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 +inline void Foo::non_static_member_function1() { using Fail = typename T::fail; } + +template +void Foo::non_static_member_function2() { using Fail = typename T::fail; } + +template +inline void Foo::static_member_function1() { using Fail = typename T::fail; } + +template +void Foo::static_member_function2() { using Fail = typename T::fail; } + +template +int Foo::static_data_member = T::fail; + +// expected-no-diagnostics +template struct Foo; Index: test/SemaCXX/attr-exclude_from_explicit_instantiation.extern_declaration.cpp =================================================================== --- test/SemaCXX/attr-exclude_from_explicit_instantiation.extern_declaration.cpp +++ test/SemaCXX/attr-exclude_from_explicit_instantiation.extern_declaration.cpp @@ -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 +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 +inline void Foo::non_static_member_function1() { + using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}} +} + +template +void Foo::non_static_member_function2() { + using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}} +} + +template +inline void Foo::static_member_function1() { + using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}} +} + +template +void Foo::static_member_function2() { + using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}} +} + +template +int Foo::static_data_member = T::invalid; // expected-error{{no member named 'invalid' in 'Empty'}} + +struct Empty { }; +extern template struct Foo; + +int main() { + Foo foo; + foo.non_static_member_function1(); // expected-note{{in instantiation of}} + foo.non_static_member_function2(); // expected-note{{in instantiation of}} + Foo::static_member_function1(); // expected-note{{in instantiation of}} + Foo::static_member_function2(); // expected-note{{in instantiation of}} + (void)foo.static_data_member; // expected-note{{in instantiation of}} + Foo::member_class1::static_member_function(); // expected-note{{in instantiation of}} + Foo::member_class2::static_member_function(); // expected-note{{in instantiation of}} +} Index: test/SemaCXX/attr-exclude_from_explicit_instantiation.merge_redeclarations.cpp =================================================================== --- test/SemaCXX/attr-exclude_from_explicit_instantiation.merge_redeclarations.cpp +++ test/SemaCXX/attr-exclude_from_explicit_instantiation.merge_redeclarations.cpp @@ -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 +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 +EXCLUDE_FROM_EXPLICIT_INSTANTIATION void Foo::func1() { + using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}} +} + +template +void Foo::func2() { + using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}} +} + +template +EXCLUDE_FROM_EXPLICIT_INSTANTIATION void Foo::func3() { + using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}} +} + +struct Empty { }; +extern template struct Foo; + +int main() { + Foo foo; + foo.func1(); // expected-note{{in instantiation of}} + foo.func2(); // expected-note{{in instantiation of}} + foo.func3(); // expected-note{{in instantiation of}} +}