diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -722,6 +722,25 @@ pushEHDestroy(dtorKind, LHS.getAddress(*this), FieldType); } +bool CodeGenModule::isBaseStructorEmitted(GlobalDecl GD) { + assert((isa(GD.getDecl()) && + GD.getCtorType() == Ctor_Complete) || + (isa(GD.getDecl()) && + GD.getDtorType() == Dtor_Complete)); + // For a non-final class, the base structor version is always emitted + // whenever the complete version is emitted. + if (!cast(GD.getDecl())->getParent()->isEffectivelyFinal()) + return true; + // For a final class, the ABI still requires the base structor to be emitted + // with the complete object structor, but GCC 10 does not do this. Don't emit + // a reference from an available_externally complete object structor to a + // potentially-non-existent base subobject structor. + // + // See https://github.com/itanium-cxx-abi/cxx-abi/issues/104. + return !llvm::GlobalValue::isAvailableExternallyLinkage( + getFunctionLinkage(GD)); +} + /// Checks whether the given constructor is a valid subject for the /// complete-to-base constructor delegation optimization, i.e. /// emitting the complete constructor as a simple call to the base @@ -849,7 +868,8 @@ // Before we go any further, try the complete->base constructor // delegation optimization. if (CtorType == Ctor_Complete && IsConstructorDelegationValid(Ctor) && - CGM.getTarget().getCXXABI().hasConstructorVariants()) { + CGM.getTarget().getCXXABI().hasConstructorVariants() && + CGM.isBaseStructorEmitted(CurGD)) { EmitDelegateCXXConstructorCall(Ctor, Ctor_Base, Args, Ctor->getEndLoc()); return; } @@ -1502,7 +1522,7 @@ // Enter the cleanup scopes for virtual bases. EnterDtorCleanups(Dtor, Dtor_Complete); - if (!isTryBody) { + if (!isTryBody && CGM.isBaseStructorEmitted(CurGD)) { QualType ThisTy = Dtor->getThisObjectType(); EmitCXXDestructorCall(Dtor, Dtor_Base, /*ForVirtualBase=*/false, /*Delegating=*/false, LoadCXXThisAddress(), ThisTy); diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -710,6 +710,11 @@ CtorList &getGlobalCtors() { return GlobalCtors; } CtorList &getGlobalDtors() { return GlobalDtors; } + /// For a GlobalDecl representing a complete-object constructor or destructor + /// that we will emit, determine whether we know that the base subobject + /// constructor or destructor will definitely be emitted if referenced. + bool isBaseStructorEmitted(GlobalDecl GD); + /// getTBAATypeInfo - Get metadata used to describe accesses to objects of /// the given type. llvm::MDNode *getTBAATypeInfo(QualType QTy); diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -1594,6 +1594,7 @@ // The constructor used for constructing this as a base class; // ignores virtual bases. + // FIXME: Skip this for final classes once the ABI permits us to. CGM.EmitGlobal(GlobalDecl(D, Ctor_Base)); // The constructor used for constructing this as a complete class; @@ -1626,6 +1627,7 @@ void ItaniumCXXABI::EmitCXXDestructors(const CXXDestructorDecl *D) { // The destructor used for destructing this as a base class; ignores // virtual bases. + // FIXME: Skip this for final classes once the ABI permits us to. CGM.EmitGlobal(GlobalDecl(D, Dtor_Base)); // The destructor used for destructing this as a most-derived class; @@ -4050,6 +4052,12 @@ const auto *CD = cast(MD); AliasDecl = GlobalDecl(CD, Ctor_Complete); } + + // If we don't know that the base class version will definitely exist, we + // can't emit a reference to it. + if (!CGM.isBaseStructorEmitted(AliasDecl)) + return StructorCodegen::Emit; + llvm::GlobalValue::LinkageTypes Linkage = CGM.getFunctionLinkage(AliasDecl); if (llvm::GlobalValue::isDiscardableIfUnused(Linkage)) diff --git a/clang/test/CodeGenCXX/final-structor-delegation.cpp b/clang/test/CodeGenCXX/final-structor-delegation.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/final-structor-delegation.cpp @@ -0,0 +1,56 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -emit-llvm %s -O2 -disable-llvm-passes -o /tmp/noalias +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -emit-llvm %s -O2 -disable-llvm-passes -mconstructor-aliases -o /tmp/alias +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -emit-llvm %s -O2 -disable-llvm-passes -o - | FileCheck %s --check-prefixes=CHECK,NOALIAS +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -emit-llvm %s -O2 -disable-llvm-passes -mconstructor-aliases -o - | FileCheck %s --check-prefixes=CHECK,ALIAS + +extern "C" void f(), g(); + +// Non-final class: we can assume that the base subobject variants of structors +// exist whenever the complete object variants exist. +template struct A { + A() { f(); } + ~A() { g(); } +}; +extern template struct A; +// CHECK-LABEL: define {{.*}}init +// NOALIAS: call void @_ZN1AIiEC1Ev({{.*}} @a) +// NOALIAS: call {{.*}}cxa_atexit{{.*}} @_ZN1AIiED1Ev {{.*}} @a +// ALIAS: call void @_ZN1AIiEC2Ev({{.*}} @a) +// ALIAS: call {{.*}}cxa_atexit{{.*}} @_ZN1AIiED2Ev {{.*}} @a +A a; + +// NOALIAS-LABEL: define available_externally {{.*}} @_ZN1AIiEC1Ev( +// NOALIAS-NOT: call void @f( +// NOALIAS: call void @_ZN1AIiEC2Ev( +// NOALIAS-NOT: call void @f( +// NOALIAS: } + +// NOALIAS-LABEL: define available_externally {{.*}} @_ZN1AIiED1Ev( +// NOALIAS-NOT: call void @g( +// NOALIAS: call void @_ZN1AIiED2Ev( +// NOALIAS-NOT: call void @g( +// NOALIAS: } + +// Final class: we can't assume that the base subobject variants exist, because +// GCC 10 doesn't emit them (even though the ABI requires them). +template struct B final { + B() { f(); } + ~B() { g(); } +}; +extern template struct B; +// CHECK-LABEL: define {{.*}}init +// CHECK: call void @_ZN1BIiEC1Ev({{.*}} @b) +// CHECK: call {{.*}}cxa_atexit{{.*}} @_ZN1BIiED1Ev {{.*}} @b +B b; + +// CHECK-LABEL: define available_externally {{.*}} @_ZN1BIiEC1Ev( +// CHECK-NOT: call void @_ZN1BIiEC2Ev( +// CHECK: call void @f( +// CHECK-NOT: call void @_ZN1BIiEC2Ev( +// CHECK: } + +// CHECK-LABEL: define available_externally {{.*}} @_ZN1BIiED1Ev( +// CHECK-NOT: call void @_ZN1BIiED2Ev( +// CHECK: call void @g( +// CHECK-NOT: call void @_ZN1BIiED2Ev( +// CHECK: }