https://github.com/Fznamznon updated https://github.com/llvm/llvm-project/pull/185653
>From 572b12dde33c986e3dade70a25de9c0802835cfc Mon Sep 17 00:00:00 2001 From: "Podchishchaeva, Mariya" <[email protected]> Date: Wed, 4 Mar 2026 05:21:09 -0800 Subject: [PATCH 1/7] [clang][win] Define vector deleting dtor body for declared-only dtors if needed Currently vector deleting destructor body emission is triggered if new[] is called for the type and if the destructor or the whole class is marked with attribute dllexport. The problem is that it is not emitted if new[] is called, destructor is not exported and it is only declared in the TU and defined in another. That makes vector deleting destructor body missing which leads to runtime failures in delete[]. This change forces vector deleting destructor body emission if new[] is called even if the destructor is only declared but not defined. Doing that replicates MSVC behavior and fixes runtime issues. Since vector deleting destructors have weak linkage, it should be safe to do so. Fixes https://github.com/llvm/llvm-project/issues/183255 AI usage: Claude was used to create LIT test cases which then were reviewed and reworked by me. --- clang/lib/AST/ASTContext.cpp | 2 + clang/lib/CodeGen/CGExprCXX.cpp | 4 + clang/lib/CodeGen/CodeGenModule.cpp | 5 + clang/lib/Sema/SemaDeclCXX.cpp | 2 + clang/lib/Sema/SemaExprCXX.cpp | 24 +++- ...rosoft-vector-deleting-dtors-new-array.cpp | 115 ++++++++++++++++++ clang/test/SemaCXX/gh134265.cpp | 13 ++ 7 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 clang/test/CodeGenCXX/microsoft-vector-deleting-dtors-new-array.cpp diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 5fbdff280073f..7d98764b57989 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13631,6 +13631,8 @@ bool ASTContext::classNeedsVectorDeletingDestructor(const CXXRecordDecl *RD) { if (!getTargetInfo().emitVectorDeletingDtors(getLangOpts())) return false; CXXDestructorDecl *Dtor = RD->getDestructor(); + if (!Dtor || !Dtor->isVirtual()) + return false; // The compiler can't know if new[]/delete[] will be used outside of the DLL, // so just force vector deleting destructor emission if dllexport is present. // This matches MSVC behavior. diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 6caef19d3be0f..17e790e3ddeb5 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -1202,6 +1202,10 @@ void CodeGenFunction::EmitNewArrayInitializer( EmitCXXAggrConstructorCall(Ctor, NumElements, CurPtr, CCE, /*NewPointerIsChecked*/ true, CCE->requiresZeroInitialization()); + if (getContext().classNeedsVectorDeletingDestructor(Ctor->getParent())) { + CXXDestructorDecl *Dtor = Ctor->getParent()->getDestructor(); + CGM.EmitGlobal(GlobalDecl(Dtor, Dtor_VectorDeleting)); + } return; } diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 3b64be7a477d6..11480374697c2 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -4374,6 +4374,11 @@ void CodeGenModule::EmitGlobal(GlobalDecl GD) { // Forward declarations are emitted lazily on first use. if (!FD->doesThisDeclarationHaveABody()) { + if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) + if (GD.getDtorType() == Dtor_VectorDeleting && + Context.classNeedsVectorDeletingDestructor(Dtor->getParent())) + addDeferredDeclToEmit(GD); + if (!FD->doesDeclarationForceExternallyVisibleDefinition() && (!FD->isMultiVersion() || !getTarget().getTriple().isAArch64())) return; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 5837ecd6b9163..941e14d3c8af7 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -19047,6 +19047,8 @@ void Sema::MarkVTableUsed(SourceLocation Loc, CXXRecordDecl *Class, // delete(). ContextRAII SavedContext(*this, DD); CheckDestructor(DD); + if (!DD->getOperatorDelete()) + DD->setInvalidDecl(); } else { MarkFunctionReferenced(Loc, Class->getDestructor()); } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 31b3a06bf10d0..9b77592d0ba60 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2640,11 +2640,25 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, // are required for delete[] call but MSVC triggers emission of them // whenever new[] is called for an object of the class and we do the same // for compatibility. - if (const CXXConstructExpr *CCE = - dyn_cast_or_null<CXXConstructExpr>(Initializer); - CCE && ArraySize) { - Context.setClassNeedsVectorDeletingDestructor( - CCE->getConstructor()->getParent()); + if (Context.getTargetInfo().emitVectorDeletingDtors(Context.getLangOpts())) { + if (const CXXConstructExpr *CCE = + dyn_cast_or_null<CXXConstructExpr>(Initializer); + CCE && ArraySize) { + CXXRecordDecl *ClassDecl = CCE->getConstructor()->getParent(); + auto *Dtor = ClassDecl->getDestructor(); + if (Dtor && Dtor->isVirtual() && !Dtor->isDeleted()) { + Context.setClassNeedsVectorDeletingDestructor(ClassDecl); + if (!Dtor->isDefined() && !Dtor->isInvalidDecl()) { + // Call CheckDestructor even if Destructor is not defined. This is + // needed to find operators delete and delete[] for vector deleting + // destructor body because new[] will trigger emission of vector + // deleting destructor body even if destructor is defined in another + // translation unit. + ContextRAII SavedContext(*this, Dtor); + CheckDestructor(Dtor); + } + } + } } return CXXNewExpr::Create(Context, UseGlobal, OperatorNew, OperatorDelete, diff --git a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors-new-array.cpp b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors-new-array.cpp new file mode 100644 index 0000000000000..fd720e5d1a7e8 --- /dev/null +++ b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors-new-array.cpp @@ -0,0 +1,115 @@ +// RUN: %clang_cc1 -emit-llvm -fms-extensions %s -triple=x86_64-pc-windows-msvc -o - | FileCheck %s + +// Test that vector deleting destructors are emitted when new[] is used, +// even when the destructor definition is in another translation unit. + +struct ForwardDeclared { + ForwardDeclared(); + virtual ~ForwardDeclared(); +}; + +struct DefinedInTU { + virtual ~DefinedInTU(); +}; + +struct NonVirtualDtor { + ~NonVirtualDtor(); +}; + +struct NoDtor { + virtual void foo(); + int x; +}; + +struct DeclDerived : ForwardDeclared { + ~DeclDerived() override; +}; + +struct InlineDefaulted { + virtual ~InlineDefaulted() = default; +}; + +struct OutOfLineDefaulted { + virtual ~OutOfLineDefaulted(); +}; + +OutOfLineDefaulted::~OutOfLineDefaulted() = default; + +template<typename T> +struct Container { + T data; + virtual ~Container(); +}; + +extern template class Container<int>; +Container<int> *arr = new Container<int>[5]; + +struct ImplicitVDtorDerived : ForwardDeclared{ + int data; +}; + +struct __declspec(dllimport) DllImported { + virtual ~DllImported(); +}; + +struct VirtualDerived : virtual ForwardDeclared { + ~VirtualDerived() override; +}; + +struct TemplateNotAllocated { + TemplateNotAllocated(); + virtual ~TemplateNotAllocated(); +}; + +struct TemplateAllocated { + TemplateAllocated(); + virtual ~TemplateAllocated(); +}; + +template <int T> +void allocate() { + TemplateNotAllocated *arr = new TemplateNotAllocated[T]; +} + +template <typename T> +void actuallyAllocate() { + T *arr = new T[10]; + delete[] arr; +} + +void cases() { + ForwardDeclared *arr = new ForwardDeclared[5]; + DefinedInTU *arr1 = new DefinedInTU[5]; + NonVirtualDtor *arr2 = new NonVirtualDtor[5]; + NoDtor *arr3 = new NoDtor[5]; + ForwardDeclared *arr4 = new DeclDerived[5]; + InlineDefaulted *arr5 = new InlineDefaulted[5]; + OutOfLineDefaulted *arr6 = new OutOfLineDefaulted[5]; + ImplicitVDtorDerived *arr7 = new ImplicitVDtorDerived[5]; + DllImported *arr8 = new DllImported[5]; + VirtualDerived *arr9 = new VirtualDerived[3]; + actuallyAllocate<TemplateAllocated>(); +} + + +// CHECK-DAG: declare dso_local void @"??1ForwardDeclared@@UEAA@XZ"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EForwardDeclared@@UEAAPEAXI@Z"( +// CHECK-DAG: define dso_local void @"??1DefinedInTU@@UEAA@XZ"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EDefinedInTU@@UEAAPEAXI@Z"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EDeclDerived@@UEAAPEAXI@Z"( +// CHECK-DAG: declare dso_local void @"??1DeclDerived@@UEAA@XZ"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EInlineDefaulted@@UEAAPEAXI@Z"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EOutOfLineDefaulted@@UEAAPEAXI@Z"( +// CHECK-DAG: declare dso_local void @"??1?$Container@H@@UEAA@XZ"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_E?$Container@H@@UEAAPEAXI@Z"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EImplicitVDtorDerived@@UEAAPEAXI@Z"( +// CHECK-DAG: declare dllimport void @"??1DllImported@@UEAA@XZ"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EDllImported@@UEAAPEAXI@Z"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EVirtualDerived@@UEAAPEAXI@Z"( +// CHECK-DAG: declare dso_local void @"??1TemplateAllocated@@UEAA@XZ"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_ETemplateAllocated@@UEAAPEAXI@Z"( +// CHECK-NOT: @"??_ETemplateNotAllocated@@ +// CHECK-NOT: @"??_ENonVirtualDtor@@ +// CHECK-NOT: @"??_ENoDtor@@ + +DefinedInTU::~DefinedInTU() {} diff --git a/clang/test/SemaCXX/gh134265.cpp b/clang/test/SemaCXX/gh134265.cpp index 790165411c938..1c0ff6627e053 100644 --- a/clang/test/SemaCXX/gh134265.cpp +++ b/clang/test/SemaCXX/gh134265.cpp @@ -56,7 +56,20 @@ struct Final1 : BaseDelete1, BaseDelete2, BaseDestructor { }; #endif // MS +// Make sure there is no double diagnosing for delcared only destructors. +struct DeclaredOnly { + virtual ~DeclaredOnly(); // ms-error {{attempt to use a deleted function}} + static void operator delete(void* ptr) = delete; // ms-note {{explicitly marked deleted here}} +}; + +struct DeclaredOnlyArr { + virtual ~DeclaredOnlyArr(); + static void operator delete[](void* ptr) = delete; +}; + void foo() { Final* a = new Final[10](); FinalExplicit* b = new FinalExplicit[10](); + DeclaredOnly *d = new DeclaredOnly[5](); + DeclaredOnlyArr *e = new DeclaredOnlyArr[5](); } >From 5b7fca7ed5e7dfc3dab06963554f592234d4fa89 Mon Sep 17 00:00:00 2001 From: "Podchishchaeva, Mariya" <[email protected]> Date: Wed, 11 Mar 2026 03:38:36 -0700 Subject: [PATCH 2/7] Add one more check to verify that there is no double diagnostics ... due to one more CheckDestructor call that this patch brings. --- clang/test/SemaCXX/gh134265.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/clang/test/SemaCXX/gh134265.cpp b/clang/test/SemaCXX/gh134265.cpp index 1c0ff6627e053..421197cf3d7f7 100644 --- a/clang/test/SemaCXX/gh134265.cpp +++ b/clang/test/SemaCXX/gh134265.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 %s -verify=expected -fsyntax-only -triple=x86_64-unknown-linux-gnu -// RUN: %clang_cc1 %s -verify=expected -fsyntax-only -triple=x86_64-unknown-linux-gnu -std=c++20 +// RUN: %clang_cc1 %s -verify=expected,noms -fsyntax-only -triple=x86_64-unknown-linux-gnu +// RUN: %clang_cc1 %s -verify=expected,noms -fsyntax-only -triple=x86_64-unknown-linux-gnu -std=c++20 // RUN: %clang_cc1 %s -verify=expected,ms -fms-extensions -fms-compatibility -triple=x86_64-pc-windows-msvc -DMS // Verify that clang doesn't emit additional errors when searching for @@ -56,7 +56,8 @@ struct Final1 : BaseDelete1, BaseDelete2, BaseDestructor { }; #endif // MS -// Make sure there is no double diagnosing for delcared only destructors. +// Make sure there is no double diagnosing for declared-only destructors and +// new[]. struct DeclaredOnly { virtual ~DeclaredOnly(); // ms-error {{attempt to use a deleted function}} static void operator delete(void* ptr) = delete; // ms-note {{explicitly marked deleted here}} @@ -73,3 +74,16 @@ void foo() { DeclaredOnly *d = new DeclaredOnly[5](); DeclaredOnlyArr *e = new DeclaredOnlyArr[5](); } + +// Make sure there is no double diagnosing for forward declared destructors +// and new[]. +namespace std { struct destroying_delete_t {}; } +struct A { + void operator delete( + A*, //expected-error {{cannot cast 'D' to its private base class 'A'}} + std::destroying_delete_t); +}; +struct B : private A { using A::operator delete; }; //expected-note {{declared private here}} +struct D : B { virtual ~D(); }; //ms-note {{while checking implicit 'delete this' for virtual destructor}} +void f() { new D[5]; } +D::~D() {} // noms-note {{while checking implicit 'delete this' for virtual destructor}} >From c7bbfc3d71f87edbead778b9d79355bc191e413a Mon Sep 17 00:00:00 2001 From: "Podchishchaeva, Mariya" <[email protected]> Date: Wed, 11 Mar 2026 03:49:02 -0700 Subject: [PATCH 3/7] Cleanup checks in astcontext --- clang/lib/AST/ASTContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 3fd33bbd80642..5583b6ecee1cf 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13647,7 +13647,7 @@ bool ASTContext::classNeedsVectorDeletingDestructor(const CXXRecordDecl *RD) { // The compiler can't know if new[]/delete[] will be used outside of the DLL, // so just force vector deleting destructor emission if dllexport is present. // This matches MSVC behavior. - if (Dtor && Dtor->isVirtual() && Dtor->hasAttr<DLLExportAttr>()) + if (Dtor->hasAttr<DLLExportAttr>()) return true; return RequireVectorDeletingDtor.count(RD); >From 46dc753bd6cf3a6a041830677f23a1a5b7aa45ba Mon Sep 17 00:00:00 2001 From: "Podchishchaeva, Mariya" <[email protected]> Date: Wed, 11 Mar 2026 10:23:07 -0700 Subject: [PATCH 4/7] Do not differentiate between declared and defined dtors, emit vector deleting dtor always if required --- clang/lib/CodeGen/CodeGenModule.cpp | 11 +++++++---- .../microsoft-vector-deleting-dtors-new-array.cpp | 7 +++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 11480374697c2..bcd8e95f9a4cf 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -4372,12 +4372,15 @@ void CodeGenModule::EmitGlobal(GlobalDecl GD) { DeferredAnnotations[MangledName] = FD; } + // Emit vector deleting dtors if required even if the destructor itself is + // not defined. + if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) + if (GD.getDtorType() == Dtor_VectorDeleting && + Context.classNeedsVectorDeletingDestructor(Dtor->getParent())) + addDeferredDeclToEmit(GD); + // Forward declarations are emitted lazily on first use. if (!FD->doesThisDeclarationHaveABody()) { - if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) - if (GD.getDtorType() == Dtor_VectorDeleting && - Context.classNeedsVectorDeletingDestructor(Dtor->getParent())) - addDeferredDeclToEmit(GD); if (!FD->doesDeclarationForceExternallyVisibleDefinition() && (!FD->isMultiVersion() || !getTarget().getTriple().isAArch64())) diff --git a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors-new-array.cpp b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors-new-array.cpp index fd720e5d1a7e8..b8b6e44b6b2f8 100644 --- a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors-new-array.cpp +++ b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors-new-array.cpp @@ -56,6 +56,11 @@ struct VirtualDerived : virtual ForwardDeclared { ~VirtualDerived() override; }; +struct DeclaredCtorDefinedDtor { + DeclaredCtorDefinedDtor(); + virtual ~DeclaredCtorDefinedDtor() {} +}; + struct TemplateNotAllocated { TemplateNotAllocated(); virtual ~TemplateNotAllocated(); @@ -88,6 +93,7 @@ void cases() { ImplicitVDtorDerived *arr7 = new ImplicitVDtorDerived[5]; DllImported *arr8 = new DllImported[5]; VirtualDerived *arr9 = new VirtualDerived[3]; + DeclaredCtorDefinedDtor *arr10 = new DeclaredCtorDefinedDtor[5]; actuallyAllocate<TemplateAllocated>(); } @@ -106,6 +112,7 @@ void cases() { // CHECK-DAG: declare dllimport void @"??1DllImported@@UEAA@XZ"( // CHECK-DAG: define weak dso_local noundef ptr @"??_EDllImported@@UEAAPEAXI@Z"( // CHECK-DAG: define weak dso_local noundef ptr @"??_EVirtualDerived@@UEAAPEAXI@Z"( +// CHECK-DAG: define weak dso_local noundef ptr @"??_EDeclaredCtorDefinedDtor@@UEAAPEAXI@Z"( // CHECK-DAG: declare dso_local void @"??1TemplateAllocated@@UEAA@XZ"( // CHECK-DAG: define weak dso_local noundef ptr @"??_ETemplateAllocated@@UEAAPEAXI@Z"( // CHECK-NOT: @"??_ETemplateNotAllocated@@ >From ae153749cc644b362c7dfecb5144613e7e00e0be Mon Sep 17 00:00:00 2001 From: "Podchishchaeva, Mariya" <[email protected]> Date: Thu, 12 Mar 2026 08:37:44 -0700 Subject: [PATCH 5/7] Move detection of new[] to CodeGen --- clang/lib/CodeGen/CGExprCXX.cpp | 8 +++- clang/lib/CodeGen/CodeGenModule.cpp | 57 +++++++++++++++++++++++---- clang/lib/CodeGen/CodeGenModule.h | 22 +++++++++-- clang/lib/CodeGen/MicrosoftCXXABI.cpp | 2 +- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index ed8e9c648fef2..9e95e509efc1d 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -1201,9 +1201,13 @@ void CodeGenFunction::EmitNewArrayInitializer( EmitCXXAggrConstructorCall(Ctor, NumElements, CurPtr, CCE, /*NewPointerIsChecked*/ true, CCE->requiresZeroInitialization()); - if (getContext().classNeedsVectorDeletingDestructor(Ctor->getParent())) { + if (getContext().getTargetInfo().emitVectorDeletingDtors( + getContext().getLangOpts())) { CXXDestructorDecl *Dtor = Ctor->getParent()->getDestructor(); - CGM.EmitGlobal(GlobalDecl(Dtor, Dtor_VectorDeleting)); + if (Dtor && Dtor->isVirtual()) { + CGM.addDeferredDeclToEmit(GlobalDecl(Dtor, Dtor_VectorDeleting)); + CGM.requireVectorDestructorDefinition(Ctor->getParent()); + } } return; } diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index bcd8e95f9a4cf..7e6318844a03e 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -4372,16 +4372,8 @@ void CodeGenModule::EmitGlobal(GlobalDecl GD) { DeferredAnnotations[MangledName] = FD; } - // Emit vector deleting dtors if required even if the destructor itself is - // not defined. - if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) - if (GD.getDtorType() == Dtor_VectorDeleting && - Context.classNeedsVectorDeletingDestructor(Dtor->getParent())) - addDeferredDeclToEmit(GD); - // Forward declarations are emitted lazily on first use. if (!FD->doesThisDeclarationHaveABody()) { - if (!FD->doesDeclarationForceExternallyVisibleDefinition() && (!FD->isMultiVersion() || !getTarget().getTriple().isAArch64())) return; @@ -8533,3 +8525,52 @@ std::string CodeGenModule::getPFPFieldName(const FieldDecl *FD) { Out << "." << FD->getName(); return OutName; } + +bool CodeGenModule::classNeedsVectorDestructor(const CXXRecordDecl *RD) { + if (!Context.getTargetInfo().emitVectorDeletingDtors(Context.getLangOpts())) + return false; + CXXDestructorDecl *Dtor = RD->getDestructor(); + // The compiler can't know if new[]/delete[] will be used outside of the DLL, + // so just force vector deleting destructor emission if dllexport is present. + // This matches MSVC behavior. + if (Dtor && Dtor->isVirtual() && Dtor->hasAttr<DLLExportAttr>()) + return true; + + return RequireVectorDeletingDtor.count(RD); +} + +void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) { + if (!Context.getTargetInfo().emitVectorDeletingDtors(Context.getLangOpts())) + return; + RequireVectorDeletingDtor.insert(RD); + + // To reduce code size in general case we lazily emit scalar deleting + // destructor definition and an alias from vector deleting destructor to + // scalar deleting destructor. It may happen that we first emitted the scalar + // deleting destructor definition and the alias and then discovered that the + // definition of the vector deleting destructor is required. Then we need to + // remove the alias and the scalar deleting destructor and queue vector + // deleting destructor body for emission. Check if that is the case. + CXXDestructorDecl *DtorD = RD->getDestructor(); + GlobalDecl ScalarDtorGD(DtorD, Dtor_Deleting); + StringRef MangledName = getMangledName(ScalarDtorGD); + llvm::GlobalValue *Entry = GetGlobalValue(MangledName); + if (Entry && !Entry->isDeclaration()) { + GlobalDecl VectorDtorGD(DtorD, Dtor_VectorDeleting); + StringRef VDName = getMangledName(VectorDtorGD); + llvm::GlobalValue *VDEntry = GetGlobalValue(VDName); + // It exists and it should be an alias. + assert(VDEntry && isa<llvm::GlobalAlias>(VDEntry)); + auto *NewFn = llvm::Function::Create( + cast<llvm::FunctionType>(VDEntry->getValueType()), + llvm::Function::ExternalLinkage, VDName, &getModule()); + SetFunctionAttributes(VectorDtorGD, NewFn, /*IsIncompleteFunction*/ false, + /*IsThunk*/ false); + NewFn->takeName(VDEntry); + VDEntry->replaceAllUsesWith(NewFn); + VDEntry->eraseFromParent(); + Entry->replaceAllUsesWith(NewFn); + Entry->eraseFromParent(); + addDeferredDeclToEmit(VectorDtorGD); + } +} diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 0081bf5c4cf5f..a0421237045c0 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -400,10 +400,6 @@ class CodeGenModule : public CodeGenTypeCache { /// This is a list of deferred decls which we have seen that *are* actually /// referenced. These get code generated when the module is done. std::vector<GlobalDecl> DeferredDeclsToEmit; - void addDeferredDeclToEmit(GlobalDecl GD) { - DeferredDeclsToEmit.emplace_back(GD); - addEmittedDeferredDecl(GD); - } /// Decls that were DeferredDecls and have now been emitted. llvm::DenseMap<llvm::StringRef, GlobalDecl> EmittedDeferredDecls; @@ -529,6 +525,11 @@ class CodeGenModule : public CodeGenTypeCache { /// that we don't re-emit the initializer. llvm::DenseMap<const Decl*, unsigned> DelayedCXXInitPosition; + /// To remember which types did require a vector deleting destructor body. + /// This set basically contains classes that have virtual destructor and new[] + /// was emitted for the class. + llvm::SmallPtrSet<const CXXRecordDecl *, 16> RequireVectorDeletingDtor; + typedef std::pair<OrderGlobalInitsOrStermFinalizers, llvm::Function *> GlobalInitData; @@ -1578,6 +1579,18 @@ class CodeGenModule : public CodeGenTypeCache { /// are emitted lazily. void EmitGlobal(GlobalDecl D); + void addDeferredDeclToEmit(GlobalDecl GD) { + DeferredDeclsToEmit.emplace_back(GD); + addEmittedDeferredDecl(GD); + } + + /// Record that new[] was called for the class, transform vector deleting + /// destructor definition in a form of alias to the actual definition. + void requireVectorDestructorDefinition(const CXXRecordDecl *RD); + + /// Check that class need vector deleting destructor body. + bool classNeedsVectorDestructor(const CXXRecordDecl *RD); + bool TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D); void EmitDefinitionAsAlias(GlobalDecl Alias, GlobalDecl Target); @@ -2092,6 +2105,7 @@ class CodeGenModule : public CodeGenTypeCache { /// Emit deactivation symbols for any PFP fields whose offset is taken with /// offsetof. void emitPFPFieldsWithEvaluatedOffset(); + }; } // end namespace CodeGen diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index 06fce6171eb28..d959b89f860e4 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -4107,7 +4107,7 @@ void MicrosoftCXXABI::emitCXXStructor(GlobalDecl GD) { return; if (GD.getDtorType() == Dtor_VectorDeleting && - !getContext().classNeedsVectorDeletingDestructor(dtor->getParent())) { + !CGM.classNeedsVectorDestructor(dtor->getParent())) { // Create GlobalDecl object with the correct type for the scalar // deleting destructor. GlobalDecl ScalarDtorGD(dtor, Dtor_Deleting); >From 363296205f1422bbd853305394042cbe202dd3e3 Mon Sep 17 00:00:00 2001 From: "Podchishchaeva, Mariya" <[email protected]> Date: Thu, 12 Mar 2026 09:47:48 -0700 Subject: [PATCH 6/7] Simplify semantic checks --- clang/include/clang/AST/ASTContext.h | 9 ++++---- clang/lib/AST/ASTContext.cpp | 18 +++++---------- clang/lib/Sema/SemaDeclCXX.cpp | 8 +++++-- clang/lib/Sema/SemaExprCXX.cpp | 34 ++++++++++++++-------------- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 05302c30d18d1..8a3d202871cf8 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -389,8 +389,9 @@ class ASTContext : public RefCountedBase<ASTContext> { mutable llvm::DenseMap<const CXXDestructorDecl *, FunctionDecl *> GlobalArrayOperatorDeletesForVirtualDtor; - /// To remember which types did require a vector deleting dtor. - llvm::DenseSet<const CXXRecordDecl *> RequireVectorDeletingDtor; + /// To remember for which types we met new[] call, these potentially require a + /// vector deleting dtor. + llvm::DenseSet<const CXXRecordDecl *> MaybeRequireVectorDeletingDtor; /// The next string literal "version" to allocate during constant evaluation. /// This is used to distinguish between repeated evaluations of the same @@ -3561,8 +3562,8 @@ class ASTContext : public RefCountedBase<ASTContext> { OperatorDeleteKind K) const; bool dtorHasOperatorDelete(const CXXDestructorDecl *Dtor, OperatorDeleteKind K) const; - void setClassNeedsVectorDeletingDestructor(const CXXRecordDecl *RD); - bool classNeedsVectorDeletingDestructor(const CXXRecordDecl *RD); + void setClassMaybeNeedsVectorDeletingDestructor(const CXXRecordDecl *RD); + bool classMaybeNeedsVectorDeletingDestructor(const CXXRecordDecl *RD); /// Retrieve the context for computing mangling numbers in the given /// DeclContext. diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 5583b6ecee1cf..d703da8413739 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13638,26 +13638,20 @@ ASTContext::getOperatorDeleteForVDtor(const CXXDestructorDecl *Dtor, return nullptr; } -bool ASTContext::classNeedsVectorDeletingDestructor(const CXXRecordDecl *RD) { +bool ASTContext::classMaybeNeedsVectorDeletingDestructor( + const CXXRecordDecl *RD) { if (!getTargetInfo().emitVectorDeletingDtors(getLangOpts())) return false; - CXXDestructorDecl *Dtor = RD->getDestructor(); - if (!Dtor || !Dtor->isVirtual()) - return false; - // The compiler can't know if new[]/delete[] will be used outside of the DLL, - // so just force vector deleting destructor emission if dllexport is present. - // This matches MSVC behavior. - if (Dtor->hasAttr<DLLExportAttr>()) - return true; - return RequireVectorDeletingDtor.count(RD); + return MaybeRequireVectorDeletingDtor.count(RD); } -void ASTContext::setClassNeedsVectorDeletingDestructor( +void ASTContext::setClassMaybeNeedsVectorDeletingDestructor( const CXXRecordDecl *RD) { if (!getTargetInfo().emitVectorDeletingDtors(getLangOpts())) return; - RequireVectorDeletingDtor.insert(RD); + + MaybeRequireVectorDeletingDtor.insert(RD); } MangleNumberingContext & diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 1e2dfb143878d..23d35a462a483 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -11248,6 +11248,7 @@ bool Sema::CheckDestructor(CXXDestructorDecl *Destructor) { if (Context.getTargetInfo().emitVectorDeletingDtors( Context.getLangOpts())) { + bool DestructorIsExported = Destructor->hasAttr<DLLExportAttr>(); // Lookup delete[] too in case we have to emit a vector deleting dtor. DeclarationName VDeleteName = Context.DeclarationNames.getCXXOperatorName(OO_Array_Delete); @@ -11261,7 +11262,8 @@ bool Sema::CheckDestructor(CXXDestructorDecl *Destructor) { VDeleteName); Destructor->setGlobalOperatorArrayDelete(GlobalArrOperatorDelete); if (GlobalArrOperatorDelete && - Context.classNeedsVectorDeletingDestructor(RD)) + (Context.classMaybeNeedsVectorDeletingDestructor(RD) || + DestructorIsExported)) MarkFunctionReferenced(Loc, GlobalArrOperatorDelete); } else if (!ArrOperatorDelete) { ArrOperatorDelete = FindDeallocationFunctionForDestructor( @@ -11269,7 +11271,9 @@ bool Sema::CheckDestructor(CXXDestructorDecl *Destructor) { /*LookForGlobal*/ true, VDeleteName); } Destructor->setOperatorArrayDelete(ArrOperatorDelete); - if (ArrOperatorDelete && Context.classNeedsVectorDeletingDestructor(RD)) + if (ArrOperatorDelete && + (Context.classMaybeNeedsVectorDeletingDestructor(RD) || + DestructorIsExported)) MarkFunctionReferenced(Loc, ArrOperatorDelete); } } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index bbc62bc76a709..5de4a1e7475f2 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2636,28 +2636,28 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, MarkFunctionReferenced(StartLoc, OperatorDelete); } - // For MSVC vector deleting destructors support we record that for the class - // new[] was called. We try to optimize the code size and only emit vector - // deleting destructors when they are required. Vector deleting destructors - // are required for delete[] call but MSVC triggers emission of them - // whenever new[] is called for an object of the class and we do the same - // for compatibility. + // new[] will trigger vector deleting destructor emission if the class has + // virtual destructor for MSVC compatibility. Perform necessary checks. if (Context.getTargetInfo().emitVectorDeletingDtors(Context.getLangOpts())) { if (const CXXConstructExpr *CCE = dyn_cast_or_null<CXXConstructExpr>(Initializer); CCE && ArraySize) { CXXRecordDecl *ClassDecl = CCE->getConstructor()->getParent(); - auto *Dtor = ClassDecl->getDestructor(); - if (Dtor && Dtor->isVirtual() && !Dtor->isDeleted()) { - Context.setClassNeedsVectorDeletingDestructor(ClassDecl); - if (!Dtor->isDefined() && !Dtor->isInvalidDecl()) { - // Call CheckDestructor even if Destructor is not defined. This is - // needed to find operators delete and delete[] for vector deleting - // destructor body because new[] will trigger emission of vector - // deleting destructor body even if destructor is defined in another - // translation unit. - ContextRAII SavedContext(*this, Dtor); - CheckDestructor(Dtor); + // We probably already did this for another new[] with this class so don't + // do it twice. + if (!Context.classMaybeNeedsVectorDeletingDestructor(ClassDecl)) { + auto *Dtor = ClassDecl->getDestructor(); + if (Dtor && Dtor->isVirtual() && !Dtor->isDeleted()) { + Context.setClassMaybeNeedsVectorDeletingDestructor(ClassDecl); + if (!Dtor->isDefined() && !Dtor->isInvalidDecl()) { + // Call CheckDestructor if destructor is not defined. This is + // needed to find operators delete and delete[] for vector deleting + // destructor body because new[] will trigger emission of vector + // deleting destructor body even if destructor is defined in another + // translation unit. + ContextRAII SavedContext(*this, Dtor); + CheckDestructor(Dtor); + } } } } >From 11b43849f8990be096c61d23523da8b776e1a9bc Mon Sep 17 00:00:00 2001 From: "Podchishchaeva, Mariya" <[email protected]> Date: Mon, 16 Mar 2026 01:47:03 -0700 Subject: [PATCH 7/7] Make format happy --- clang/lib/CodeGen/CodeGenModule.h | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index a0421237045c0..946b3488ddde6 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -2105,7 +2105,6 @@ class CodeGenModule : public CodeGenTypeCache { /// Emit deactivation symbols for any PFP fields whose offset is taken with /// offsetof. void emitPFPFieldsWithEvaluatedOffset(); - }; } // end namespace CodeGen _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
