Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -3042,6 +3042,13 @@ let Documentation = [InternalLinkageDocs]; } +def NoExternTemplate : InheritableAttr { + let Spellings = [Clang<"no_extern_template">]; + let Subjects = SubjectList<[Var, Function, CXXRecord]>; + let Documentation = [NoExternTemplateDocs]; + let MeaningfulToClassTemplateDefinition = 1; +} + def Reinitializes : InheritableAttr { let Spellings = [Clang<"reinitializes", 0>]; let Subjects = SubjectList<[NonStaticNonConstCXXMethod], ErrorDiag>; Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -2975,6 +2975,22 @@ }]; } +def NoExternTemplateDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``no_extern_template`` 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 ``no_extern_template``, 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). 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: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4679,6 +4679,13 @@ "'internal_linkage' attribute on a non-static local variable is ignored">, InGroup; +def warn_no_extern_template_undefined_entity : Warning< + "Member '%0' marked with 'no_extern_template' attribute is not defined but an " + "explicit template instantiation declaration exists. Reliance on this member " + "being defined by an explicit template instantiation will lead to link errors.">; +def note_explicit_template_instantiation_declaration_here: Note< + "explicit template instantiation declaration is here">; + def ext_internal_in_extern_inline : ExtWarn< "static %select{function|variable}0 %1 is used in an inline function with " "external linkage">, InGroup; Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -6464,6 +6464,9 @@ case ParsedAttr::AT_InternalLinkage: handleInternalLinkageAttr(S, D, AL); break; + case ParsedAttr::AT_NoExternTemplate: + handleSimpleAttribute(S, D, AL); + break; case ParsedAttr::AT_LTOVisibilityPublic: handleSimpleAttribute(S, D, AL); break; Index: clang/lib/Sema/SemaTemplateInstantiate.cpp =================================================================== --- clang/lib/Sema/SemaTemplateInstantiate.cpp +++ clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -2576,6 +2576,20 @@ if (auto *Function = dyn_cast(D)) { if (FunctionDecl *Pattern = Function->getInstantiatedFromMemberFunction()) { + + // Skip the instantiation of this member if marked with no_extern_template. + if (Function->hasAttr()) { + if (TSK == TSK_ExplicitInstantiationDeclaration && + !Pattern->isDefined()) { + Diag(Function->getLocation(), + diag::warn_no_extern_template_undefined_entity) + << Function->getName(); + Diag(PointOfInstantiation, + diag::note_explicit_template_instantiation_declaration_here); + } + continue; + } + MemberSpecializationInfo *MSInfo = Function->getMemberSpecializationInfo(); assert(MSInfo && "No member specialization information?"); @@ -2618,6 +2632,19 @@ continue; if (Var->isStaticDataMember()) { + // Skip the instantiation of this member if marked with no_extern_template. + if (Var->hasAttr()) { + if (TSK == TSK_ExplicitInstantiationDeclaration && + !Var->getInstantiatedFromStaticDataMember()->getDefinition()) { + Diag(Var->getLocation(), + diag::warn_no_extern_template_undefined_entity) + << Var->getName(); + Diag(PointOfInstantiation, + diag::note_explicit_template_instantiation_declaration_here); + } + continue; + } + MemberSpecializationInfo *MSInfo = Var->getMemberSpecializationInfo(); assert(MSInfo && "No member specialization information?"); if (MSInfo->getTemplateSpecializationKind() @@ -2649,6 +2676,20 @@ } } } else if (auto *Record = dyn_cast(D)) { + // Skip the instantiation of this member if marked with no_extern_template. + if (Record->hasAttr()) { + if (TSK == TSK_ExplicitInstantiationDeclaration && + !Record->getDefinition() && + !Record->getInstantiatedFromMemberClass()->getDefinition()) { + Diag(Record->getLocation(), + diag::warn_no_extern_template_undefined_entity) + << Record->getName(); + Diag(PointOfInstantiation, + diag::note_explicit_template_instantiation_declaration_here); + } + 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: clang/test/CodeGenCXX/attr-no_extern_template.dont_assume_extern_instantiation.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCXX/attr-no_extern_template.dont_assume_extern_instantiation.cpp @@ -0,0 +1,83 @@ +// 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 no_extern_template 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 NO_EXTERN_TEMPLATE __attribute__((no_extern_template)) + +template +struct Foo { + NO_EXTERN_TEMPLATE inline void non_static_member_function1(); + NO_EXTERN_TEMPLATE void non_static_member_function2(); + + NO_EXTERN_TEMPLATE static inline void static_member_function1(); + NO_EXTERN_TEMPLATE static void static_member_function2(); + + NO_EXTERN_TEMPLATE static int static_data_member; + + struct NO_EXTERN_TEMPLATE member_class1 { + static void static_member_function() { } + }; + + struct member_class2 { + NO_EXTERN_TEMPLATE 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 as no_extern_template 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 as no_extern_template 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 as no_extern_template 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 as no_extern_template 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 as no_extern_template 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 as no_extern_template 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 as no_extern_template 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: clang/test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ clang/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) @@ -73,6 +73,7 @@ // CHECK-NEXT: NoDestroy (SubjectMatchRule_variable) // CHECK-NEXT: NoDuplicate (SubjectMatchRule_function) // CHECK-NEXT: NoEscape (SubjectMatchRule_variable_is_parameter) +// CHECK-NEXT: NoExternTemplate (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record) // CHECK-NEXT: NoInline (SubjectMatchRule_function) // CHECK-NEXT: NoInstrumentFunction (SubjectMatchRule_function) // CHECK-NEXT: NoMicroMips (SubjectMatchRule_function) Index: clang/test/SemaCXX/attr-no_extern_template.diagnose_on_undefined_entity.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/attr-no_extern_template.diagnose_on_undefined_entity.cpp @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// Test that a diagnostic is emitted when an entity marked with no_extern_template +// is not defined in the current TU but an extern template declaration for the +// enclosing class exists. + +#define NO_EXTERN_TEMPLATE __attribute__((no_extern_template)) + +template +struct Foo { + NO_EXTERN_TEMPLATE void non_static_member_function(); // expected-warning{{Member 'non_static_member_function' marked with 'no_extern_template' attribute is not defined but an explicit template instantiation declaration exists. Reliance on this member being defined by an explicit template instantiation will lead to link errors.}} + NO_EXTERN_TEMPLATE static void static_member_function1(); // expected-warning{{Member 'static_member_function1' marked with 'no_extern_template' attribute is not defined}} + NO_EXTERN_TEMPLATE static int static_data_member; // expected-warning{{Member 'static_data_member' marked with 'no_extern_template' attribute is not defined}} + struct NO_EXTERN_TEMPLATE member_class1; // expected-warning{{Member 'member_class1' marked with 'no_extern_template' attribute is not defined}} +}; + +extern template struct Foo; // expected-note 4 {{explicit template instantiation declaration is here}} Index: clang/test/SemaCXX/attr-no_extern_template.explicit_instantiation.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/attr-no_extern_template.explicit_instantiation.cpp @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// Test that explicit instantiations do not instantiate entities +// marked with no_extern_template. + +#define NO_EXTERN_TEMPLATE __attribute__((no_extern_template)) + +template +struct Foo { + NO_EXTERN_TEMPLATE inline void non_static_member_function1(); + + NO_EXTERN_TEMPLATE void non_static_member_function2(); + + NO_EXTERN_TEMPLATE static inline void static_member_function1(); + + NO_EXTERN_TEMPLATE static void static_member_function2(); + + NO_EXTERN_TEMPLATE static int static_data_member; + + struct NO_EXTERN_TEMPLATE member_class1 { + static void non_static_member_function() { using Fail = typename T::fail; } + }; + + struct member_class2 { + NO_EXTERN_TEMPLATE 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: clang/test/SemaCXX/attr-no_extern_template.extern_declaration.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/attr-no_extern_template.extern_declaration.cpp @@ -0,0 +1,68 @@ +// RUN: %clang_cc1 -Wno-unused-local-typedef -fsyntax-only -verify %s + +// Test that extern instantiation declarations cause members marked with +// no_extern_template to be instantiated in the current TU. + +#define NO_EXTERN_TEMPLATE __attribute__((no_extern_template)) + +template +struct Foo { + NO_EXTERN_TEMPLATE inline void non_static_member_function1(); + + NO_EXTERN_TEMPLATE void non_static_member_function2(); + + NO_EXTERN_TEMPLATE static inline void static_member_function1(); + + NO_EXTERN_TEMPLATE static void static_member_function2(); + + NO_EXTERN_TEMPLATE static int static_data_member; + + struct NO_EXTERN_TEMPLATE member_class1 { + static void static_member_function() { + using Fail = typename T::invalid; // expected-error{{no type named 'invalid' in 'Empty'}} + } + }; + + struct member_class2 { + NO_EXTERN_TEMPLATE 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: clang/test/SemaCXX/attr-no_extern_template.merge_redeclarations.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/attr-no_extern_template.merge_redeclarations.cpp @@ -0,0 +1,43 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// Test that we properly merge the no_extern_template attribute on +// redeclarations. + +#define NO_EXTERN_TEMPLATE __attribute__((no_extern_template)) + +template +struct Foo { + // Declaration without the attribute, definition with the attribute. + void func1(); + + // Declaration with the attribute, definition without the attribute. + NO_EXTERN_TEMPLATE void func2(); + + // Declaration with the attribute, definition with the attribute. + NO_EXTERN_TEMPLATE void func3(); +}; + +template +NO_EXTERN_TEMPLATE 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 +NO_EXTERN_TEMPLATE 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}} +}