Index: include/clang/AST/Decl.h =================================================================== --- include/clang/AST/Decl.h +++ include/clang/AST/Decl.h @@ -1469,6 +1469,9 @@ // has no definition within this source file. bool isKnownToBeDefined() const; + /// Do we need to emit an exit-time destructor for this variable? + bool isNoDestroy(const ASTContext &) const; + // Implement isa/cast/dyncast/etc. static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classofKind(Kind K) { return K >= firstVar && K <= lastVar; } Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -3009,3 +3009,15 @@ let Subjects = SubjectList<[NonStaticNonConstCXXMethod], ErrorDiag>; let Documentation = [ReinitializesDocs]; } + +def NoDestroy : InheritableAttr { + let Spellings = [Clang<"no_destroy", 0>]; + let Subjects = SubjectList<[Var]>; + let Documentation = [NoDestroyDocs]; +} + +def AlwaysDestroy : InheritableAttr { + let Spellings = [Clang<"always_destroy", 0>]; + let Subjects = SubjectList<[Var]>; + let Documentation = [AlwaysDestroyDocs]; +} Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -3486,3 +3486,22 @@ }; }]; } + +def AlwaysDestroyDocs : Documentation { + let Category = DocCatVariable; + let Content = [{ +The ``always_destroy`` attribute specifies that a variable with static or thread +storage duration should have its exit-time destructor run. This attribute is the +default unless clang was invoked with -fno-c++-static-destructors. + }]; +} + +def NoDestroyDocs : Documentation { + let Category = DocCatVariable; + let Content = [{ +The ``no_destroy`` attribute specifies that a variable with static or thread +storage duration shouldn't have its exit-time destructor run. Annotating every +static and thread duration variable with this attribute is equivalent to +invoking clang with -fno-c++-static-destructors. + }]; +} Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -1809,6 +1809,10 @@ def note_destructor_type_here : Note< "type %0 is declared here">; +def err_destroy_attr_on_non_static_var : Error< + "%select{no_destroy|always_destroy}0 attribute can only be applied to a" + " variable with static or thread storage duration">; + def err_destructor_template : Error< "destructor cannot be declared as a template">; Index: include/clang/Basic/LangOptions.def =================================================================== --- include/clang/Basic/LangOptions.def +++ include/clang/Basic/LangOptions.def @@ -308,6 +308,8 @@ LANGOPT(PaddingOnUnsignedFixedPoint, 1, 0, "unsigned fixed point types having one extra padding bit") +LANGOPT(RegisterStaticDestructors, 1, 1, "Register C++ static destructors") + #undef LANGOPT #undef COMPATIBLE_LANGOPT #undef BENIGN_LANGOPT Index: include/clang/Driver/Options.td =================================================================== --- include/clang/Driver/Options.td +++ include/clang/Driver/Options.td @@ -898,6 +898,13 @@ Flags<[CC1Option]>, HelpText<"Enable fixed point types">; def fno_fixed_point : Flag<["-"], "fno-fixed-point">, Group, HelpText<"Disable fixed point types">; +def fcxx_static_destructors : Flag<["-"], "fc++-static-destructors">, + Group, + HelpText<"Enable C++ static destructor registration (the default)">; +def fno_cxx_static_destructors : Flag<["-"], "fno-c++-static-destructors">, + Group, + Flags<[CC1Option]>, + HelpText<"Disable C++ static destructor registration">; // Begin sanitizer flags. These should all be core options exposed in all driver // modes. Index: lib/AST/Decl.cpp =================================================================== --- lib/AST/Decl.cpp +++ lib/AST/Decl.cpp @@ -2449,6 +2449,12 @@ return hasDefinition(); } +bool VarDecl::isNoDestroy(const ASTContext &Ctx) const { + return hasGlobalStorage() && (hasAttr() || + (!Ctx.getLangOpts().RegisterStaticDestructors && + !hasAttr())); +} + MemberSpecializationInfo *VarDecl::getMemberSpecializationInfo() const { if (isStaticDataMember()) // FIXME: Remove ? Index: lib/CodeGen/ItaniumCXXABI.cpp =================================================================== --- lib/CodeGen/ItaniumCXXABI.cpp +++ lib/CodeGen/ItaniumCXXABI.cpp @@ -2342,6 +2342,9 @@ const VarDecl &D, llvm::Constant *dtor, llvm::Constant *addr) { + if (D.isNoDestroy(CGM.getContext())) + return; + // Use __cxa_atexit if available. if (CGM.getCodeGenOpts().CXAAtExit) return emitGlobalDtorWithCXAAtExit(CGF, dtor, addr, D.getTLSKind()); Index: lib/CodeGen/MicrosoftCXXABI.cpp =================================================================== --- lib/CodeGen/MicrosoftCXXABI.cpp +++ lib/CodeGen/MicrosoftCXXABI.cpp @@ -2240,6 +2240,9 @@ void MicrosoftCXXABI::registerGlobalDtor(CodeGenFunction &CGF, const VarDecl &D, llvm::Constant *Dtor, llvm::Constant *Addr) { + if (D.isNoDestroy(CGM.getContext())) + return; + if (D.getTLSKind()) return emitGlobalDtorWithTLRegDtor(CGF, D, Dtor, Addr); Index: lib/Driver/ToolChains/Clang.cpp =================================================================== --- lib/Driver/ToolChains/Clang.cpp +++ lib/Driver/ToolChains/Clang.cpp @@ -4832,6 +4832,10 @@ options::OPT_fno_complete_member_pointers, false)) CmdArgs.push_back("-fcomplete-member-pointers"); + if (!Args.hasFlag(options::OPT_fcxx_static_destructors, + options::OPT_fno_cxx_static_destructors, true)) + CmdArgs.push_back("-fno-c++-static-destructors"); + if (Arg *A = Args.getLastArg(options::OPT_moutline, options::OPT_mno_outline)) { if (A->getOption().matches(options::OPT_moutline)) { Index: lib/Frontend/CompilerInvocation.cpp =================================================================== --- lib/Frontend/CompilerInvocation.cpp +++ lib/Frontend/CompilerInvocation.cpp @@ -2772,6 +2772,8 @@ // -fallow-editor-placeholders Opts.AllowEditorPlaceholders = Args.hasArg(OPT_fallow_editor_placeholders); + Opts.RegisterStaticDestructors = !Args.hasArg(OPT_fno_cxx_static_destructors); + if (Arg *A = Args.getLastArg(OPT_fclang_abi_compat_EQ)) { Opts.setClangABICompat(LangOptions::ClangABI::Latest); Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -5917,6 +5917,20 @@ AL.getRange(), S.Context, AL.getAttributeSpellingListIndex())); } +static void handleDestroyAttr(Sema &S, Decl *D, const ParsedAttr &A) { + if (!isa(D) || !cast(D)->hasGlobalStorage()) { + S.Diag(D->getLocation(), diag::err_destroy_attr_on_non_static_var) + << (A.getKind() == ParsedAttr::AT_AlwaysDestroy); + return; + } + + if (A.getKind() == ParsedAttr::AT_AlwaysDestroy) { + handleSimpleAttributeWithExclusions(S, D, A); + } else { + handleSimpleAttributeWithExclusions(S, D, A); + } +} + //===----------------------------------------------------------------------===// // Top Level Sema Entry Points //===----------------------------------------------------------------------===// @@ -6587,6 +6601,11 @@ case ParsedAttr::AT_Reinitializes: handleSimpleAttribute(S, D, AL); break; + + case ParsedAttr::AT_AlwaysDestroy: + case ParsedAttr::AT_NoDestroy: + handleDestroyAttr(S, D, AL); + break; } } Index: lib/Sema/SemaDeclCXX.cpp =================================================================== --- lib/Sema/SemaDeclCXX.cpp +++ lib/Sema/SemaDeclCXX.cpp @@ -12910,6 +12910,9 @@ if (ClassDecl->hasIrrelevantDestructor()) return; if (ClassDecl->isDependentContext()) return; + if (VD->isNoDestroy(getASTContext())) + return; + CXXDestructorDecl *Destructor = LookupDestructor(ClassDecl); MarkFunctionReferenced(VD->getLocation(), Destructor); CheckDestructorAccess(VD->getLocation(), Destructor, Index: test/CodeGenCXX/always_destroy.cpp =================================================================== --- test/CodeGenCXX/always_destroy.cpp +++ test/CodeGenCXX/always_destroy.cpp @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 %s -fno-c++-static-destructors -emit-llvm -triple x86_64-apple-macosx10.13.0 -o - | FileCheck %s + +struct NonTrivial { + ~NonTrivial(); +}; + +// CHECK-NOT: __cxa_atexit{{.*}}_ZN10NonTrivialD1Ev +NonTrivial nt1; +// CHECK-NOT: _tlv_atexit{{.*}}_ZN10NonTrivialD1Ev +thread_local NonTrivial nt2; + +struct NonTrivial2 { + ~NonTrivial2(); +}; + +// CHECK: __cxa_atexit{{.*}}_ZN11NonTrivial2D1Ev +[[clang::always_destroy]] NonTrivial2 nt21; +// CHECK: _tlv_atexit{{.*}}_ZN11NonTrivial2D1Ev +[[clang::always_destroy]] thread_local NonTrivial2 nt22; + +void f() { + // CHECK: __cxa_atexit{{.*}}_ZN11NonTrivial2D1Ev + [[clang::always_destroy]] static NonTrivial2 nt21; + // CHECK: _tlv_atexit{{.*}}_ZN11NonTrivial2D1Ev + [[clang::always_destroy]] thread_local NonTrivial2 nt22; +} + +// CHECK-NOT: __cxa_atexit{{.*}}_ZN10NonTrivialD1Ev +[[clang::no_destroy]] NonTrivial nt3; +// CHECK-NOT: _tlv_atexit{{.*}}_ZN10NonTrivialD1Ev +[[clang::no_destroy]] thread_local NonTrivial nt4; Index: test/CodeGenCXX/no_destroy.cpp =================================================================== --- test/CodeGenCXX/no_destroy.cpp +++ test/CodeGenCXX/no_destroy.cpp @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 %s -emit-llvm -triple x86_64-apple-macosx10.13.0 -o - | FileCheck %s + +struct NonTrivial { + ~NonTrivial(); +}; + +// CHECK-NOT: __cxa_atexit{{.*}}_ZN10NonTrivialD1Ev +[[clang::no_destroy]] NonTrivial nt1; +// CHECK-NOT: _tlv_atexit{{.*}}_ZN10NonTrivialD1Ev +[[clang::no_destroy]] thread_local NonTrivial nt2; + +struct NonTrivial2 { + ~NonTrivial2(); +}; + +// CHECK: __cxa_atexit{{.*}}_ZN11NonTrivial2D1Ev +NonTrivial2 nt21; +// CHECK: _tlv_atexit{{.*}}_ZN11NonTrivial2D1Ev +thread_local NonTrivial2 nt22; + +void f() { + // CHECK: __cxa_atexit{{.*}}_ZN11NonTrivial2D1Ev + static NonTrivial2 nt21; + // CHECK: _tlv_atexit{{.*}}_ZN11NonTrivial2D1Ev + thread_local NonTrivial2 nt22; +} + +// CHECK: __cxa_atexit{{.*}}_ZN10NonTrivialD1Ev +[[clang::always_destroy]] NonTrivial nt3; +// CHECK: _tlv_atexit{{.*}}_ZN10NonTrivialD1Ev +[[clang::always_destroy]] thread_local NonTrivial nt4; 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 72 attributes: +// CHECK: #pragma clang attribute supports 74 attributes: // CHECK-NEXT: AMDGPUFlatWorkGroupSize (SubjectMatchRule_function) // CHECK-NEXT: AMDGPUNumSGPR (SubjectMatchRule_function) // CHECK-NEXT: AMDGPUNumVGPR (SubjectMatchRule_function) @@ -11,6 +11,7 @@ // CHECK-NEXT: AbiTag (SubjectMatchRule_record_not_is_union, SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_namespace) // CHECK-NEXT: AlignValue (SubjectMatchRule_variable, SubjectMatchRule_type_alias) // CHECK-NEXT: AllocSize (SubjectMatchRule_function) +// CHECK-NEXT: AlwaysDestroy (SubjectMatchRule_variable) // CHECK-NEXT: Annotate () // CHECK-NEXT: AnyX86NoCfCheck (SubjectMatchRule_hasType_functionType) // CHECK-NEXT: AssumeAligned (SubjectMatchRule_objc_method, SubjectMatchRule_function) @@ -38,6 +39,7 @@ // CHECK-NEXT: MipsLongCall (SubjectMatchRule_function) // CHECK-NEXT: MipsShortCall (SubjectMatchRule_function) // CHECK-NEXT: NoDebug (SubjectMatchRule_hasType_functionType, SubjectMatchRule_objc_method, SubjectMatchRule_variable_not_is_parameter) +// CHECK-NEXT: NoDestroy (SubjectMatchRule_variable) // CHECK-NEXT: NoDuplicate (SubjectMatchRule_function) // CHECK-NEXT: NoEscape (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: NoMicroMips (SubjectMatchRule_function) Index: test/SemaCXX/no_destroy.cpp =================================================================== --- test/SemaCXX/no_destroy.cpp +++ test/SemaCXX/no_destroy.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -DNO_DTORS -fno-c++-static-destructors -verify %s +// RUN: %clang_cc1 -verify %s + +struct SecretDestructor { +#ifndef NO_DTORS + // expected-note@+2 4 {{private}} +#endif +private: ~SecretDestructor(); // expected-note 2 {{private}} +}; + +SecretDestructor sd1; +thread_local SecretDestructor sd2; +void locals() { + static SecretDestructor sd3; + thread_local SecretDestructor sd4; +} + +#ifndef NO_DTORS +// SecretDestructor sd1; // expected-error@-8 {{private}} +// thread_local SecretDestructor sd2; // expected-error@-8 {{private}} +// void locals() { +// static SecretDestructor sd3; // expected-error@-8 {{private}} +// thread_local SecretDestructor sd4; // expected-error@-8 {{private}} +// } +#endif + +[[clang::always_destroy]] SecretDestructor sd6; // expected-error{{private}} +[[clang::always_destroy]] thread_local SecretDestructor sd7; // expected-error{{private}} + +[[clang::no_destroy]] SecretDestructor sd8; + +int main() { + [[clang::no_destroy]] int p; // expected-error{{no_destroy attribute can only be applied to a variable with static or thread storage duration}} + [[clang::always_destroy]] int p2; // expected-error{{always_destroy attribute can only be applied to a variable with static or thread storage duration}} + [[clang::no_destroy]] static int p3; + [[clang::always_destroy]] static int p4; +} + +[[clang::always_destroy]] [[clang::no_destroy]] int p; // expected-error{{'no_destroy' and 'always_destroy' attributes are not compatible}} // expected-note{{here}} +[[clang::no_destroy]] [[clang::always_destroy]] int p2; // expected-error{{'always_destroy' and 'no_destroy' attributes are not compatible}} // expected-note{{here}} Index: test/SemaCXX/warn-exit-time-destructors.cpp =================================================================== --- test/SemaCXX/warn-exit-time-destructors.cpp +++ test/SemaCXX/warn-exit-time-destructors.cpp @@ -43,3 +43,8 @@ }; E e; } + +namespace test4 { +struct A { ~A(); }; +[[clang::no_destroy]] A a; // no warning +}