diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 89d86c3371247..bce8cf0060940 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -46,6 +46,22 @@ Potentially Breaking Changes ``endbr64`` instruction at the labels named as possible branch destinations, so it is not safe to use a register-controlled branch instruction to branch to one. (In line with gcc.) +- Scalar deleting destructor support has been aligned with MSVC when + targeting the MSVC ABI. Clang previously implemented support for + ``::delete`` by calling the complete object destructor and then the + appropriate global delete operator (as is done for the Itanium ABI). + The scalar deleting destructor is now called to destroy the object + and deallocate its storage. This is an ABI change that can result in + memory corruption when a program built for the MSVC ABI has + portions compiled with clang 20 or earlier and portions compiled + with a version of clang 21 (or MSVC). Consider a class ``X`` that + declares a virtual destructor and an ``operator delete`` member + with the destructor defined in library ``A`` and a call to `::delete`` in + library ``B``. If library ``A`` is compiled with clang 20 and library ``B`` + is compiled with clang 21, the ``::delete`` call might dispatch to the + scalar deleting destructor emitted in library ``A`` which will erroneously + call the member ``operator delete`` instead of the expected global + delete operator. C/C++ Language Potentially Breaking Changes ------------------------------------------- diff --git a/clang/include/clang/AST/ASTMutationListener.h b/clang/include/clang/AST/ASTMutationListener.h index 2c4ec2ce67f36..ab0a539f84e42 100644 --- a/clang/include/clang/AST/ASTMutationListener.h +++ b/clang/include/clang/AST/ASTMutationListener.h @@ -86,6 +86,10 @@ class ASTMutationListener { const FunctionDecl *Delete, Expr *ThisArg) {} + /// A virtual destructor's operator global delete has been resolved. + virtual void ResolvedOperatorGlobDelete(const CXXDestructorDecl *DD, + const FunctionDecl *GlobDelete) {} + /// An implicit member got a definition. virtual void CompletedImplicitDefinition(const FunctionDecl *D) {} diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 05cddd024d7cf..c20b3d08571bc 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -2854,6 +2854,7 @@ class CXXDestructorDecl : public CXXMethodDecl { // FIXME: Don't allocate storage for these except in the first declaration // of a virtual destructor. FunctionDecl *OperatorDelete = nullptr; + FunctionDecl *OperatorGlobalDelete = nullptr; Expr *OperatorDeleteThisArg = nullptr; CXXDestructorDecl(ASTContext &C, CXXRecordDecl *RD, SourceLocation StartLoc, @@ -2879,11 +2880,16 @@ class CXXDestructorDecl : public CXXMethodDecl { static CXXDestructorDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); void setOperatorDelete(FunctionDecl *OD, Expr *ThisArg); + void setOperatorGlobalDelete(FunctionDecl *OD); const FunctionDecl *getOperatorDelete() const { return getCanonicalDecl()->OperatorDelete; } + const FunctionDecl *getOperatorGlobalDelete() const { + return getCanonicalDecl()->OperatorGlobalDelete; + } + Expr *getOperatorDeleteThisArg() const { return getCanonicalDecl()->OperatorDeleteThisArg; } diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h index c3bce6e807f34..4d41e31d59cb7 100644 --- a/clang/include/clang/Basic/TargetInfo.h +++ b/clang/include/clang/Basic/TargetInfo.h @@ -1741,6 +1741,14 @@ class TargetInfo : public TransferrableTargetInfo, /// Clang backwards compatibility rather than GCC/Itanium ABI compatibility. virtual bool areDefaultedSMFStillPOD(const LangOptions&) const; + /// Controls whether global operator delete is called by the deleting + /// destructor or at the point where ::delete was called. Historically Clang + /// called global operator delete outside of the deleting destructor for both + /// Microsoft and Itanium ABI. In Clang 21 support for ::delete was aligned + /// with Microsoft ABI, so it will call global operator delete in the deleting + /// destructor body. + virtual bool callGlobalDeleteInDeletingDtor(const LangOptions &) const; + /// Controls if __builtin_longjmp / __builtin_setjmp can be lowered to /// llvm.eh.sjlj.longjmp / llvm.eh.sjlj.setjmp. virtual bool hasSjLjLowering() const { diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 9397546c8fc5d..7c04d21b5c34d 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -8523,10 +8523,12 @@ class Sema final : public SemaBase { bool Diagnose = true); FunctionDecl *FindUsualDeallocationFunction(SourceLocation StartLoc, ImplicitDeallocationParameters, - DeclarationName Name); - FunctionDecl *FindDeallocationFunctionForDestructor(SourceLocation StartLoc, - CXXRecordDecl *RD, - bool Diagnose = true); + DeclarationName Name, + bool Diagnose = true); + FunctionDecl * + FindDeallocationFunctionForDestructor(SourceLocation StartLoc, + CXXRecordDecl *RD, bool Diagnose = true, + bool LookForGlobal = false); /// ActOnCXXDelete - Parsed a C++ 'delete' expression (C++ 5.3.5), as in: /// @code ::delete ptr; @endcode diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h index 97679ace8b610..dcb7a1010b5a1 100644 --- a/clang/include/clang/Serialization/ASTWriter.h +++ b/clang/include/clang/Serialization/ASTWriter.h @@ -946,6 +946,8 @@ class ASTWriter : public ASTDeserializationListener, void ResolvedOperatorDelete(const CXXDestructorDecl *DD, const FunctionDecl *Delete, Expr *ThisArg) override; + void ResolvedOperatorGlobDelete(const CXXDestructorDecl *DD, + const FunctionDecl *Delete) override; void CompletedImplicitDefinition(const FunctionDecl *D) override; void InstantiationRequested(const ValueDecl *D) override; void VariableDefinitionInstantiated(const VarDecl *D) override; diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index ccb308e103253..f95b5480d61d2 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -3101,6 +3101,22 @@ void CXXDestructorDecl::setOperatorDelete(FunctionDecl *OD, Expr *ThisArg) { } } +void CXXDestructorDecl::setOperatorGlobalDelete(FunctionDecl *OD) { + // FIXME: C++23 [expr.delete] specifies that the delete operator will be + // a usual deallocation function declared at global scope. A convenient + // function to assert that is lacking; Sema::isUsualDeallocationFunction() + // only works for CXXMethodDecl. + assert(!OD || + (OD->getDeclName().getCXXOverloadedOperator() == OO_Delete && + OD->getDeclContext()->getRedeclContext()->isTranslationUnit())); + auto *Canonical = cast(getCanonicalDecl()); + if (!Canonical->OperatorGlobalDelete) { + Canonical->OperatorGlobalDelete = OD; + if (auto *L = getASTMutationListener()) + L->ResolvedOperatorGlobDelete(Canonical, OD); + } +} + bool CXXDestructorDecl::isCalledByDelete(const FunctionDecl *OpDel) const { // C++20 [expr.delete]p6: If the value of the operand of the delete- // expression is not a null pointer value and the selected deallocation diff --git a/clang/lib/Basic/TargetInfo.cpp b/clang/lib/Basic/TargetInfo.cpp index 9429a316a9196..5bd2d37932723 100644 --- a/clang/lib/Basic/TargetInfo.cpp +++ b/clang/lib/Basic/TargetInfo.cpp @@ -619,6 +619,14 @@ TargetInfo::getCallingConvKind(bool ClangABICompat4) const { return CCK_Default; } +bool TargetInfo::callGlobalDeleteInDeletingDtor( + const LangOptions &LangOpts) const { + if (getCXXABI() == TargetCXXABI::Microsoft && + LangOpts.getClangABICompat() > LangOptions::ClangABI::Ver20) + return true; + return false; +} + bool TargetInfo::areDefaultedSMFStillPOD(const LangOptions &LangOpts) const { return LangOpts.getClangABICompat() > LangOptions::ClangABI::Ver15; } diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 13792c1042046..7904dd43bea07 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -1587,28 +1587,84 @@ namespace { } }; + // This function implements generation of scalar deleting destructor body for + // the case when the destructor also accepts an implicit flag. Right now only + // Microsoft ABI requires deleting destructors to accept implicit flags. + // The flag indicates whether an operator delete should be called and whether + // it should be a class-specific operator delete or a global one. void EmitConditionalDtorDeleteCall(CodeGenFunction &CGF, llvm::Value *ShouldDeleteCondition, bool ReturnAfterDelete) { + const CXXDestructorDecl *Dtor = cast(CGF.CurCodeDecl); + const CXXRecordDecl *ClassDecl = Dtor->getParent(); + const FunctionDecl *OD = Dtor->getOperatorDelete(); + assert(OD->isDestroyingOperatorDelete() == ReturnAfterDelete && + "unexpected value for ReturnAfterDelete"); + auto *CondTy = cast(ShouldDeleteCondition->getType()); + // Clang 20 calls global operator delete after dtor call. Clang 21 and newer + // call global operator delete inside of dtor body, as MSVC does. + ASTContext &Context = CGF.getContext(); + bool Clang21AndNewer = + Context.getTargetInfo().callGlobalDeleteInDeletingDtor( + Context.getLangOpts()); + if (Clang21AndNewer && OD->isDestroyingOperatorDelete()) { + llvm::BasicBlock *CallDtor = CGF.createBasicBlock("dtor.call_dtor"); + llvm::BasicBlock *DontCallDtor = CGF.createBasicBlock("dtor.entry_cont"); + // Third bit set signals that global operator delete is called. That means + // despite class having destroying operator delete which is responsible + // for calling dtor, we need to call dtor because global operator delete + // won't do that. + llvm::Value *Check3rdBit = CGF.Builder.CreateAnd( + ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 4)); + llvm::Value *ShouldCallDtor = CGF.Builder.CreateIsNull(Check3rdBit); + CGF.Builder.CreateCondBr(ShouldCallDtor, DontCallDtor, CallDtor); + CGF.EmitBlock(CallDtor); + QualType ThisTy = Dtor->getFunctionObjectParameterType(); + CGF.EmitCXXDestructorCall(Dtor, Dtor_Complete, /*ForVirtualBase=*/false, + /*Delegating=*/false, CGF.LoadCXXThisAddress(), + ThisTy); + CGF.Builder.CreateBr(DontCallDtor); + CGF.EmitBlock(DontCallDtor); + } llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete"); llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue"); - llvm::Value *ShouldCallDelete - = CGF.Builder.CreateIsNull(ShouldDeleteCondition); + // First bit set signals that operator delete must be called. + llvm::Value *Check1stBit = CGF.Builder.CreateAnd( + ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 1)); + llvm::Value *ShouldCallDelete = CGF.Builder.CreateIsNull(Check1stBit); CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB); CGF.EmitBlock(callDeleteBB); - const CXXDestructorDecl *Dtor = cast(CGF.CurCodeDecl); - const CXXRecordDecl *ClassDecl = Dtor->getParent(); - CGF.EmitDeleteCall(Dtor->getOperatorDelete(), - LoadThisForDtorDelete(CGF, Dtor), - CGF.getContext().getTagDeclType(ClassDecl)); - assert(Dtor->getOperatorDelete()->isDestroyingOperatorDelete() == - ReturnAfterDelete && - "unexpected value for ReturnAfterDelete"); - if (ReturnAfterDelete) - CGF.EmitBranchThroughCleanup(CGF.ReturnBlock); - else - CGF.Builder.CreateBr(continueBB); + auto EmitDeleteAndGoToEnd = [&](const FunctionDecl *DeleteOp) { + CGF.EmitDeleteCall(DeleteOp, LoadThisForDtorDelete(CGF, Dtor), + Context.getTagDeclType(ClassDecl)); + if (ReturnAfterDelete) + CGF.EmitBranchThroughCleanup(CGF.ReturnBlock); + else + CGF.Builder.CreateBr(continueBB); + }; + // If Sema only found a global operator delete previously, the dtor can + // always call it. Otherwise we need to check the third bit and call the + // appropriate operator delete, i.e. global or class-specific. + if (const FunctionDecl *GlobOD = Dtor->getOperatorGlobalDelete(); + isa(OD) && GlobOD && Clang21AndNewer) { + // Third bit set signals that global operator delete is called, i.e. + // ::delete appears on the callsite. + llvm::Value *CheckTheBitForGlobDeleteCall = CGF.Builder.CreateAnd( + ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 4)); + llvm::Value *ShouldCallGlobDelete = + CGF.Builder.CreateIsNull(CheckTheBitForGlobDeleteCall); + llvm::BasicBlock *GlobDelete = + CGF.createBasicBlock("dtor.call_glob_delete"); + llvm::BasicBlock *ClassDelete = + CGF.createBasicBlock("dtor.call_class_delete"); + CGF.Builder.CreateCondBr(ShouldCallGlobDelete, ClassDelete, GlobDelete); + CGF.EmitBlock(GlobDelete); + + EmitDeleteAndGoToEnd(GlobOD); + CGF.EmitBlock(ClassDelete); + } + EmitDeleteAndGoToEnd(OD); CGF.EmitBlock(continueBB); } diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index a181559834296..cff0308bbd4a0 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -894,12 +894,19 @@ void MicrosoftCXXABI::emitVirtualObjectDelete(CodeGenFunction &CGF, const CXXDestructorDecl *Dtor) { // FIXME: Provide a source location here even though there's no // CXXMemberCallExpr for dtor call. - bool UseGlobalDelete = DE->isGlobalDelete(); - CXXDtorType DtorType = UseGlobalDelete ? Dtor_Complete : Dtor_Deleting; - llvm::Value *MDThis = EmitVirtualDestructorCall(CGF, Dtor, DtorType, Ptr, DE, - /*CallOrInvoke=*/nullptr); - if (UseGlobalDelete) - CGF.EmitDeleteCall(DE->getOperatorDelete(), MDThis, ElementType); + if (!getContext().getTargetInfo().callGlobalDeleteInDeletingDtor( + getContext().getLangOpts())) { + bool UseGlobalDelete = DE->isGlobalDelete(); + CXXDtorType DtorType = UseGlobalDelete ? Dtor_Complete : Dtor_Deleting; + llvm::Value *MDThis = + EmitVirtualDestructorCall(CGF, Dtor, DtorType, Ptr, DE, + /*CallOrInvoke=*/nullptr); + if (UseGlobalDelete) + CGF.EmitDeleteCall(DE->getOperatorDelete(), MDThis, ElementType); + } else { + EmitVirtualDestructorCall(CGF, Dtor, Dtor_Deleting, Ptr, DE, + /*CallOrInvoke=*/nullptr); + } } void MicrosoftCXXABI::emitRethrow(CodeGenFunction &CGF, bool isNoReturn) { @@ -2014,7 +2021,10 @@ llvm::Value *MicrosoftCXXABI::EmitVirtualDestructorCall( ASTContext &Context = getContext(); llvm::Value *ImplicitParam = llvm::ConstantInt::get( llvm::IntegerType::getInt32Ty(CGF.getLLVMContext()), - DtorType == Dtor_Deleting); + (DtorType == Dtor_Deleting) | + 4 * (D && D->isGlobalDelete() && + Context.getTargetInfo().callGlobalDeleteInDeletingDtor( + Context.getLangOpts()))); QualType ThisTy; if (CE) { diff --git a/clang/lib/Frontend/MultiplexConsumer.cpp b/clang/lib/Frontend/MultiplexConsumer.cpp index 3fd3c9bd69037..f5f8848798a35 100644 --- a/clang/lib/Frontend/MultiplexConsumer.cpp +++ b/clang/lib/Frontend/MultiplexConsumer.cpp @@ -107,6 +107,8 @@ class MultiplexASTMutationListener : public ASTMutationListener { void ResolvedOperatorDelete(const CXXDestructorDecl *DD, const FunctionDecl *Delete, Expr *ThisArg) override; + void ResolvedOperatorGlobDelete(const CXXDestructorDecl *DD, + const FunctionDecl *GlobDelete) override; void CompletedImplicitDefinition(const FunctionDecl *D) override; void InstantiationRequested(const ValueDecl *D) override; void VariableDefinitionInstantiated(const VarDecl *D) override; @@ -184,6 +186,11 @@ void MultiplexASTMutationListener::ResolvedOperatorDelete( for (auto *L : Listeners) L->ResolvedOperatorDelete(DD, Delete, ThisArg); } +void MultiplexASTMutationListener::ResolvedOperatorGlobDelete( + const CXXDestructorDecl *DD, const FunctionDecl *GlobDelete) { + for (auto *L : Listeners) + L->ResolvedOperatorGlobDelete(DD, GlobDelete); +} void MultiplexASTMutationListener::CompletedImplicitDefinition( const FunctionDecl *D) { for (size_t i = 0, e = Listeners.size(); i != e; ++i) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 16645ecf411e5..1d12a378166fd 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -11123,6 +11123,26 @@ bool Sema::CheckDestructor(CXXDestructorDecl *Destructor) { DiagnoseUseOfDecl(OperatorDelete, Loc); MarkFunctionReferenced(Loc, OperatorDelete); Destructor->setOperatorDelete(OperatorDelete, ThisArg); + + // Clang 20 calls global operator delete after dtor call. Clang 21 and + // newer call global operator delete inside of dtor body, as MSVC does. + // So we don't really need to fetch global operator delete for Clang 20 + // ABI. + if (isa(OperatorDelete) && + Context.getTargetInfo().callGlobalDeleteInDeletingDtor( + Context.getLangOpts())) { + // In Microsoft ABI whenever a class has a defined operator delete, + // scalar deleting destructors check the 3rd bit of the implicit + // parameter and if it is set, then, global operator delete must be + // called instead of the class-specific one. Find and save the global + // operator delete for that case. Do not diagnose at this point because + // the lack of a global operator delete is not an error if there are no + // delete calls that require it. + FunctionDecl *GlobalOperatorDelete = + FindDeallocationFunctionForDestructor(Loc, RD, /*Diagnose*/ false, + /*LookForGlobal*/ true); + Destructor->setOperatorGlobalDelete(GlobalOperatorDelete); + } } } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 4a86cbd0633b6..acf5e466f81cd 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -3554,7 +3554,7 @@ void Sema::DeclareGlobalAllocationFunction(DeclarationName Name, FunctionDecl * Sema::FindUsualDeallocationFunction(SourceLocation StartLoc, ImplicitDeallocationParameters IDP, - DeclarationName Name) { + DeclarationName Name, bool Diagnose) { DeclareGlobalNewDelete(); LookupResult FoundDelete(*this, Name, StartLoc, LookupOrdinaryName); @@ -3569,7 +3569,7 @@ Sema::FindUsualDeallocationFunction(SourceLocation StartLoc, if (!Result) return nullptr; - if (CheckDeleteOperator(*this, StartLoc, StartLoc, /*Diagnose=*/true, + if (CheckDeleteOperator(*this, StartLoc, StartLoc, Diagnose, FoundDelete.getNamingClass(), Result.Found, Result.FD)) return nullptr; @@ -3580,7 +3580,8 @@ Sema::FindUsualDeallocationFunction(SourceLocation StartLoc, FunctionDecl *Sema::FindDeallocationFunctionForDestructor(SourceLocation Loc, CXXRecordDecl *RD, - bool Diagnose) { + bool Diagnose, + bool LookForGlobal) { DeclarationName Name = Context.DeclarationNames.getCXXOperatorName(OO_Delete); FunctionDecl *OperatorDelete = nullptr; @@ -3589,18 +3590,20 @@ FunctionDecl *Sema::FindDeallocationFunctionForDestructor(SourceLocation Loc, DeallocType, ShouldUseTypeAwareOperatorNewOrDelete(), AlignedAllocationMode::No, SizedDeallocationMode::No}; - if (FindDeallocationFunction(Loc, RD, Name, OperatorDelete, IDP, Diagnose)) - return nullptr; + if (!LookForGlobal) { + if (FindDeallocationFunction(Loc, RD, Name, OperatorDelete, IDP, Diagnose)) + return nullptr; - if (OperatorDelete) - return OperatorDelete; + if (OperatorDelete) + return OperatorDelete; + } // If there's no class-specific operator delete, look up the global // non-array delete. IDP.PassAlignment = alignedAllocationModeFromBool( hasNewExtendedAlignment(*this, DeallocType)); IDP.PassSize = SizedDeallocationMode::Yes; - return FindUsualDeallocationFunction(Loc, IDP, Name); + return FindUsualDeallocationFunction(Loc, IDP, Name, Diagnose); } bool Sema::FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD, diff --git a/clang/lib/Serialization/ASTCommon.h b/clang/lib/Serialization/ASTCommon.h index 371db4bace013..c9b9b1bbf8743 100644 --- a/clang/lib/Serialization/ASTCommon.h +++ b/clang/lib/Serialization/ASTCommon.h @@ -41,7 +41,8 @@ enum class DeclUpdateKind { DeclMarkedOpenMPAllocate, DeclMarkedOpenMPDeclareTarget, DeclExported, - AddedAttrToRecord + AddedAttrToRecord, + CXXResolvedDtorGlobDelete }; TypeIdx TypeIdxFromBuiltin(const BuiltinType *BT); diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp index 0ffd78424be0d..ad9259a43e79f 100644 --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -2340,8 +2340,8 @@ void ASTDeclReader::VisitCXXConstructorDecl(CXXConstructorDecl *D) { void ASTDeclReader::VisitCXXDestructorDecl(CXXDestructorDecl *D) { VisitCXXMethodDecl(D); + CXXDestructorDecl *Canon = D->getCanonicalDecl(); if (auto *OperatorDelete = readDeclAs()) { - CXXDestructorDecl *Canon = D->getCanonicalDecl(); auto *ThisArg = Record.readExpr(); // FIXME: Check consistency if we have an old and new operator delete. if (!Canon->OperatorDelete) { @@ -2349,6 +2349,11 @@ void ASTDeclReader::VisitCXXDestructorDecl(CXXDestructorDecl *D) { Canon->OperatorDeleteThisArg = ThisArg; } } + if (auto *OperatorGlobDelete = readDeclAs()) { + if (!Canon->OperatorGlobalDelete) { + Canon->OperatorGlobalDelete = OperatorGlobDelete; + } + } } void ASTDeclReader::VisitCXXConversionDecl(CXXConversionDecl *D) { @@ -4854,6 +4859,14 @@ void ASTDeclReader::UpdateDecl(Decl *D) { break; } + case DeclUpdateKind::CXXResolvedDtorGlobDelete: { + auto *Del = readDeclAs(); + auto *Canon = cast(D->getCanonicalDecl()); + if (!Canon->OperatorGlobalDelete) + Canon->OperatorGlobalDelete = Del; + break; + } + case DeclUpdateKind::CXXResolvedExceptionSpec: { SmallVector ExceptionStorage; auto ESI = Record.readExceptionSpecInfo(ExceptionStorage); diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index c6487c5366a29..ba0e0b0a43f23 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -6506,6 +6506,10 @@ void ASTWriter::WriteDeclUpdatesBlocks(ASTContext &Context, Record.AddStmt(cast(D)->getOperatorDeleteThisArg()); break; + case DeclUpdateKind::CXXResolvedDtorGlobDelete: + Record.AddDeclRef(Update.getDecl()); + break; + case DeclUpdateKind::CXXResolvedExceptionSpec: { auto prototype = cast(D)->getType()->castAs(); @@ -7559,6 +7563,20 @@ void ASTWriter::ResolvedOperatorDelete(const CXXDestructorDecl *DD, }); } +void ASTWriter::ResolvedOperatorGlobDelete(const CXXDestructorDecl *DD, + const FunctionDecl *GlobDelete) { + if (Chain && Chain->isProcessingUpdateRecords()) + return; + assert(!WritingAST && "Already writing the AST!"); + assert(GlobDelete && "Not given an operator delete"); + if (!Chain) + return; + Chain->forEachImportedKeyDecl(DD, [&](const Decl *D) { + DeclUpdates[D].push_back( + DeclUpdate(DeclUpdateKind::CXXResolvedDtorGlobDelete, GlobDelete)); + }); +} + void ASTWriter::CompletedImplicitDefinition(const FunctionDecl *D) { if (Chain && Chain->isProcessingUpdateRecords()) return; assert(!WritingAST && "Already writing the AST!"); diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp index 2e390dbe79ec6..557ec6ef59fd3 100644 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -1793,6 +1793,7 @@ void ASTDeclWriter::VisitCXXDestructorDecl(CXXDestructorDecl *D) { Record.AddDeclRef(D->getOperatorDelete()); if (D->getOperatorDelete()) Record.AddStmt(D->getOperatorDeleteThisArg()); + Record.AddDeclRef(D->getOperatorGlobalDelete()); Code = serialization::DECL_CXX_DESTRUCTOR; } diff --git a/clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp b/clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp index f6f4a2ff735cc..2971bf67e4b37 100644 --- a/clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp +++ b/clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp @@ -1,6 +1,8 @@ // RUN: %clang_cc1 -std=c++2a -fexceptions -emit-llvm %s -triple x86_64-linux-gnu -o - | FileCheck %s --check-prefixes=CHECK,CHECK-ITANIUM,CHECK-64BIT -// RUN: %clang_cc1 -std=c++2a -fexceptions -emit-llvm %s -triple x86_64-windows -o - | FileCheck %s --check-prefixes=CHECK,CHECK-MSABI,CHECK-MSABI64,CHECK-64BIT -// RUN: %clang_cc1 -std=c++2a -fexceptions -emit-llvm %s -triple i386-windows -o - | FileCheck %s --check-prefixes=CHECK,CHECK-MSABI,CHECK-MSABI32,CHECK-32BIT +// RUN: %clang_cc1 -std=c++2a -fexceptions -emit-llvm %s -triple x86_64-windows -o - | FileCheck %s --check-prefixes=CHECK,CHECK-MSABI,CHECK-MSABI64,CHECK-64BIT,CLANG21-MSABI,CLANG21-MSABI64 +// RUN: %clang_cc1 -std=c++2a -fexceptions -emit-llvm %s -triple i386-windows -o - | FileCheck %s --check-prefixes=CHECK,CHECK-MSABI,CHECK-MSABI32,CHECK-32BIT,CLANG21-MSABI,CLANG21-MSABI32 +// RUN: %clang_cc1 -std=c++2a -fexceptions -emit-llvm %s -triple i386-windows -fclang-abi-compat=20 -o - | FileCheck %s --check-prefixes=CHECK,CHECK-MSABI,CHECK-32BIT,CHECK-MSABI32,CLANG20-MSABI,CLANG20-MSABI32 +// RUN: %clang_cc1 -std=c++2a -fexceptions -emit-llvm %s -triple x86_64-windows -fclang-abi-compat=20 -o - | FileCheck %s --check-prefixes=CHECK,CHECK-MSABI,CHECK-MSABI64,CHECK-64BIT,CLANG20-MSABI,CLANG20-MSABI64 // PR46908: ensure the IR passes the verifier with optimizations enabled. // RUN: %clang_cc1 -std=c++2a -fexceptions -emit-llvm-only %s -triple x86_64-linux-gnu -O2 @@ -32,6 +34,20 @@ void delete_A(A *a) { delete a; } // CHECK-NOT: call // CHECK: } +void glob_delete_A(A *a) { ::delete a; } + +// CHECK-LABEL: define {{.*}}glob_delete_A +// CHECK: %[[a:.*]] = load +// CHECK: icmp eq ptr %[[a]], null +// CHECK: br i1 + +// CHECK-ITANIUM: call void @_ZN1AD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %[[a]]) +// CHECK-ITANIUM-NEXT: call void @_ZdlPvm(ptr noundef %[[a]], i64 noundef 8) +// CHECK-MSABI64: call void @"??1A@@QEAA@XZ"(ptr noundef nonnull align 8 dereferenceable(8) %[[a]]) +// CHECK-MSABI64-NEXT: call void @"??3@YAXPEAX_K@Z"(ptr noundef %[[a]], i64 noundef 8) +// CHECK-MSABI32: call x86_thiscallcc void @"??1A@@QAE@XZ"(ptr noundef nonnull align 4 dereferenceable(4) %[[a]]) +// CHECK-MSABI32-NEXT: call void @"??3@YAXPAXI@Z"(ptr noundef %[[a]], i32 noundef 4) + struct B { virtual ~B(); void operator delete(B*, std::destroying_delete_t); @@ -51,6 +67,31 @@ void delete_B(B *b) { delete b; } // CHECK-NOT: call // CHECK: } +void glob_delete_B(B *b) { ::delete b; } +// CHECK-LABEL: define {{.*}}glob_delete_B +// CHECK: %[[b:.*]] = load +// CHECK: icmp eq ptr %[[b]], null +// CHECK: br i1 + +// CHECK-NOT: call +// CHECK-MSABI: %[[VTABLE:.*]] = load +// CHECK-MSABI: %[[DTOR:.*]] = load +// CHECK-ITANIUM: %[[VTABLE:.*]] = load ptr, ptr %[[b]], align 8 +// CHECK-ITANIUM: %[[COMPLETEOFFSETPTR:.*]] = getelementptr inbounds i64, ptr %[[VTABLE]], i64 -2 +// CHECK-ITANIUM: %[[OFFSET:.*]] = load i64, ptr %[[COMPLETEOFFSETPTR]], align 8 +// CHECK-ITANIUM: %[[ALLOCATED:.*]] = getelementptr inbounds i8, ptr %[[b]], i64 %[[OFFSET]] +// CHECK-ITANIUM: %[[VTABLE1:.*]] = load ptr, ptr %[[b]], align 8 +// CHECK-ITANIUM: %[[DTOR_ADDR:.*]] = getelementptr inbounds ptr, ptr %[[VTABLE1]], i64 0 +// CHECK-ITANIUM: %[[DTOR:.*]] = load ptr, ptr %[[DTOR_ADDR]], align 8 +// CHECK: call {{void|noundef ptr|x86_thiscallcc noundef ptr}} %[[DTOR]](ptr {{[^,]*}} %[[b]] +// CLANG21-MSABI-SAME: , i32 noundef 5) +// CLANG20-MSABI-SAME: , i32 noundef 0) +// CLANG21-MSABI-NOT: call +// CLANG20-MSABI64: call void @"??3@YAXPEAX_K@Z"({{.*}}) +// CLANG20-MSABI32: call void @"??3@YAXPAXI@Z"({{.*}}) +// CHECK-ITANIUM: call void @_ZdlPvm({{.*}}) +// CHECK: } + struct Padding { virtual void f(); }; @@ -159,21 +200,50 @@ H::~H() { call_in_dtor(); } // CHECK-ITANIUM-NOT: call // CHECK-ITANIUM: } -// CHECK-MSABI64-LABEL: define {{.*}} @"??_GH@@UEAAPEAXI@Z"( +// CHECK-MSABI64-LABEL: define {{.*}} @"??_GH@@UEAAPEAXI@Z"({{.*}}, // CHECK-MSABI32-LABEL: define {{.*}} @"??_GH@@UAEPAXI@Z"( +// CHECK-MSABI-SAME: i32 noundef %[[IP:.*]]) // CHECK-MSABI-NOT: call{{ }} -// CHECK-MSABI: load i32 -// CHECK-MSABI: icmp eq i32 {{.*}}, 0 -// CHECK-MSABI: br i1 +// CHECK-MSABI: store i32 %[[IP]], ptr %[[IP_ALLOCA:.*]] +// CHECK-MSABI: %[[IMP_PARAM:.*]] = load i32, ptr %[[IP_ALLOCA]] +// CLANG21: %[[THIRDBIT:.*]] = and i32 %[[IMP_PARAM]], 4 +// CLANG21-NEXT: %[[CHCK:.*]] = icmp eq i32 %[[THIRDBIT]], 0 +// CLANG21-NEXT: br i1 %[[CHCK]], label %dtor.entry_cont, label %dtor.call_dtor +// CLANG20-MSABI: %[[FIRSTBIT:.*]] = and i32 %[[IMP_PARAM]], 1 +// CLANG20-MSABI: %[[CHCK:.*]] = icmp eq i32 %[[FIRSTBIT]], 0 +// CLANG20-MSABI: br i1 %[[CHCK]], label %dtor.continue, label %dtor.call_delete // -// CHECK-MSABI-NOT: call{{ }} -// CHECK-MSABI64: getelementptr {{.*}}, i64 24 -// CHECK-MSABI32: getelementptr {{.*}}, i32 20 -// CHECK-MSABI-NOT: call{{ }} +// CLANG21-MSABI: dtor.call_dtor: +// CLANG21-MSABI64-NEXT: call void @"??1H@@UEAA@XZ"({{.*}}) +// CLANG21-MSABI32-NEXT: call x86_thiscallcc void @"??1H@@UAE@XZ"({{.*}}) +// CLANG21-MSABI-NEXT: br label %dtor.entry_cont +// +// CLANG21-MSABI-LABEL: dtor.entry_cont: +// CLANG21-MSABI-NEXT: %[[FIRSTBIT:.*]] = and i32 %[[IMP_PARAM]], 1 +// CLANG21-MSABI-NEXT: %[[CHCK1:.*]] = icmp eq i32 %[[FIRSTBIT]], 0 +// CLANG21-MSABI-NEXT: br i1 %[[CHCK1]], label %dtor.continue, label %dtor.call_delete +// +// CLANG21-MSABI-LABEL: dtor.call_delete: +// CLANG21-MSABI: %[[THIRDBIT1:.*]] = and i32 %[[IMP_PARAM]], 4 +// CLANG21-MSABI-NEXT: %[[CHCK2:.*]] = icmp eq i32 %[[THIRDBIT1]], 0 +// CLANG21-MSABI-NEXT: br i1 %[[CHCK2]], label %dtor.call_class_delete, label %dtor.call_glob_delete +// +// CLANG21-MSABI-LABEL: dtor.call_glob_delete: +// CLANG21-MSABI64: call void @"??3@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef 48) +// CLANG21-MSABI32: call void @"??3@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 32, i32 noundef 16) +// CLANG21-MSABI-NEXT: br label %[[RETURN:.*]] +// +// CLANG20-MSABI: dtor.call_delete: +// CLANG21-MSABI: dtor.call_class_delete: +// CLANG21-MSABI-NOT: call{{ }} +// CLANG21-MSABI64: getelementptr {{.*}}, i64 24 +// CLANG21-MSABI32: getelementptr {{.*}}, i32 20 +// CLANG21-MSABI-NOT: call{{ }} // CHECK-MSABI64: call void @"??3F@@SAXPEAU0@Udestroying_delete_t@std@@_KW4align_val_t@2@@Z"({{.*}}, i64 noundef 48, i64 noundef 16) // CHECK-MSABI32: call void @"??3F@@SAXPAU0@Udestroying_delete_t@std@@IW4align_val_t@2@@Z"({{.*}}, i32 noundef 32, i32 noundef 16) -// CHECK-MSABI: br label %[[RETURN:.*]] +// CHECK-MSABI: br label %[[RETURN:]] // +// CHECK-MSABI: dtor.continue: // CHECK-MSABI64: call void @"??1H@@UEAA@XZ"( // CHECK-MSABI32: call x86_thiscallcc void @"??1H@@UAE@XZ"( // CHECK-MSABI: br label %[[RETURN]] @@ -194,9 +264,32 @@ I::~I() { call_in_dtor(); } // CHECK-MSABI32-LABEL: define {{.*}} @"??_GI@@UAEPAXI@Z"( // CHECK-MSABI-NOT: call{{ }} // CHECK-MSABI: load i32 -// CHECK-MSABI: icmp eq i32 {{.*}}, 0 -// CHECK-MSABI: br i1 +// CLANG21-MSABI-NEXT: and i32 %[[IMP_PARAM:.*]], 4 +// CLANG21-MSABI-NEXT: icmp eq i32 {{.*}}, 0 +// CLANG21-MSABI-NEXT: br i1 %{{.*}}, label %dtor.entry_cont, label %dtor.call_dtor +// +// CLANG21-MSABI: dtor.call_dtor: +// CLANG21-MSABI64-NEXT: call void @"??1I@@UEAA@XZ"({{.*}}) +// CLANG21-MSABI32-NEXT: call x86_thiscallcc void @"??1I@@UAE@XZ"({{.*}}) +// CLANG21-MSABI-NEXT: br label %dtor.entry_cont +// +// CLANG21-MSABI: dtor.entry_cont: +// CLANG21-MSABI-NEXT: and i32 %[[IMP_PARAM]], 1 +// CLANG21-MSABI-NEXT: icmp eq i32 %{{.*}}, 0 +// CLANG21-MSABI-NEXT: br i1 %{{.*}}, label %dtor.continue, label %dtor.call_delete +// +// CLANG21-MSABI: dtor.call_delete: +// CLANG21-MSABI-NEXT: %[[THIRDBIT1:.*]] = and i32 %[[IMP_PARAM]], 4 +// CLANG21-MSABI-NEXT: %[[CHCK2:.*]] = icmp eq i32 %[[THIRDBIT1]], 0 +// CLANG21-MSABI-NEXT: br i1 %[[CHCK2]], label %dtor.call_class_delete, label %dtor.call_glob_delete +// +// CLANG21-MSABI: dtor.call_glob_delete: +// CLANG21-MSABI64: call void @"??3@YAXPEAX_KW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i64 noundef 96, i64 noundef 32) +// CLANG21-MSABI32: call void @"??3@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 64, i32 noundef 32) +// CLANG21-MSABI-NEXT: br label %[[RETURN:.*]] // +// CLANG20-MSABI: dtor.call_delete: +// CLANG21-MSABI: dtor.call_class_delete: // CHECK-MSABI-NOT: call{{ }} // CHECK-MSABI64: getelementptr {{.*}}, i64 24 // CHECK-MSABI32: getelementptr {{.*}}, i32 20 diff --git a/clang/test/CodeGenCXX/microsoft-abi-structors.cpp b/clang/test/CodeGenCXX/microsoft-abi-structors.cpp index 07abc3d065e5e..26db01a39bc02 100644 --- a/clang/test/CodeGenCXX/microsoft-abi-structors.cpp +++ b/clang/test/CodeGenCXX/microsoft-abi-structors.cpp @@ -1,5 +1,5 @@ // RUN: %clang_cc1 -no-enable-noundef-analysis -emit-llvm -fno-rtti %s -std=c++11 -o - -mconstructor-aliases -triple=i386-pc-win32 -fno-rtti > %t -// RUN: FileCheck %s < %t +// RUN: FileCheck --check-prefixes CHECK,CLANG21 %s < %t // vftables are emitted very late, so do another pass to try to keep the checks // in source order. // RUN: FileCheck --check-prefix DTORS %s < %t @@ -8,6 +8,7 @@ // RUN: FileCheck --check-prefix DTORS4 %s < %t // // RUN: %clang_cc1 -emit-llvm %s -o - -mconstructor-aliases -triple=x86_64-pc-win32 -fno-rtti -std=c++11 | FileCheck --check-prefix DTORS-X64 %s +// RUN: %clang_cc1 -no-enable-noundef-analysis -emit-llvm -fno-rtti %s -std=c++11 -o - -mconstructor-aliases -triple=i386-pc-win32 -fclang-abi-compat=20 | FileCheck --check-prefixes CHECK,CLANG20 %s namespace basic { @@ -52,7 +53,8 @@ struct C { // DTORS: store ptr %{{.*}}, ptr %[[RETVAL:retval]] // DTORS: %[[SHOULD_DELETE_VALUE:[0-9a-z._]+]] = load i32, ptr %[[SHOULD_DELETE_VAR]] // DTORS: call x86_thiscallcc void @"??1C@basic@@UAE@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]]) -// DTORS-NEXT: %[[CONDITION:[0-9]+]] = icmp eq i32 %[[SHOULD_DELETE_VALUE]], 0 +// DTORS-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 1 +// DTORS-NEXT: %[[CONDITION:[0-9]+]] = icmp eq i32 %[[AND]], 0 // DTORS-NEXT: br i1 %[[CONDITION]], label %[[CONTINUE_LABEL:[0-9a-z._]+]], label %[[CALL_DELETE_LABEL:[0-9a-z._]+]] // // DTORS: [[CALL_DELETE_LABEL]] @@ -113,8 +115,9 @@ void call_deleting_dtor_and_global_delete(C *obj_ptr) { // CHECK-NEXT: %[[VTABLE:.*]] = load ptr, ptr %[[OBJ_PTR_VALUE]] // CHECK-NEXT: %[[PVDTOR:.*]] = getelementptr inbounds ptr, ptr %[[VTABLE]], i64 0 // CHECK-NEXT: %[[VDTOR:.*]] = load ptr, ptr %[[PVDTOR]] -// CHECK-NEXT: %[[CALL:.*]] = call x86_thiscallcc ptr %[[VDTOR]](ptr {{[^,]*}} %[[OBJ_PTR_VALUE]], i32 0) -// CHECK-NEXT: call void @"??3@YAXPAX@Z"(ptr %[[CALL]]) +// CLANG21-NEXT: %[[CALL:.*]] = call x86_thiscallcc ptr %[[VDTOR]](ptr {{[^,]*}} %[[OBJ_PTR_VALUE]], i32 5) +// CLANG20-NEXT: %[[CALL:.*]] = call x86_thiscallcc ptr %[[VDTOR]](ptr {{[^,]*}} %[[OBJ_PTR_VALUE]], i32 0) +// CLANG20-NEXT: call void @"??3@YAXPAX@Z"(ptr %[[CALL]]) // CHECK: ret void } @@ -458,3 +461,57 @@ class G { extern void testG() { G g; } + +namespace operator_delete { + +class H { virtual ~H(); + void operator delete(void *); +}; +H::~H() { } + +void checkH() { + new H(); +} +// DTORS: define linkonce_odr dso_local x86_thiscallcc ptr @"??_GH@operator_delete@@EAEPAXI@Z"(ptr {{[^,]*}} %this, i32 %should_call_delete) {{.*}} comdat {{.*}} { +// DTORS: store i32 %should_call_delete, ptr %[[SHOULD_DELETE_VAR:[0-9a-z._]+]], align 4 +// DTORS: store ptr %{{.*}}, ptr %[[RETVAL:retval]] +// DTORS: %[[SHOULD_DELETE_VALUE:[0-9a-z._]+]] = load i32, ptr %[[SHOULD_DELETE_VAR]] +// DTORS: call x86_thiscallcc void @"??1H@operator_delete@@EAE@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]]) +// DTORS-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 1 +// DTORS-NEXT: %[[CONDITION:[0-9]+]] = icmp eq i32 %[[AND]], 0 +// DTORS-NEXT: br i1 %[[CONDITION]], label %[[CONTINUE_LABEL:[0-9a-z._]+]], label %[[CALL_DELETE_LABEL:[0-9a-z._]+]] +// +// DTORS: [[CALL_DELETE_LABEL]] +// DTORS-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 4 +// DTORS-NEXT: %[[CONDITION1:[0-9]+]] = icmp eq i32 %[[AND]], 0 +// DTORS-NEXT: br i1 %[[CONDITION1]], label %[[CALL_CLASS_DELETE:[0-9a-z._]+]], label %[[CALL_GLOB_DELETE:[0-9a-z._]+]] +// +// DTORS: [[CALL_GLOB_DELETE]] +// DTORS-NEXT: call void @"??3@YAXPAX@Z"(ptr %[[THIS]]) +// DTORS-NEXT: br label %[[CONTINUE_LABEL]] +// +// DTORS: [[CALL_CLASS_DELETE]] +// DTORS-NEXT: call void @"??3H@operator_delete@@CAXPAX@Z"(ptr %[[THIS]]) +// DTORS-NEXT: br label %[[CONTINUE_LABEL]] +// +// DTORS: [[CONTINUE_LABEL]] +// DTORS-NEXT: %[[RET:.*]] = load ptr, ptr %[[RETVAL]] +// DTORS-NEXT: ret ptr %[[RET]] + +// CLANG20: define linkonce_odr dso_local x86_thiscallcc ptr @"??_GH@operator_delete@@EAEPAXI@Z"(ptr {{[^,]*}} %this, i32 %should_call_delete) {{.*}} comdat {{.*}} { +// CLANG20: store i32 %should_call_delete, ptr %[[SHOULD_DELETE_VAR:[0-9a-z._]+]], align 4 +// CLANG20: store ptr %{{.*}}, ptr %[[RETVAL:retval]] +// CLANG20: %[[SHOULD_DELETE_VALUE:[0-9a-z._]+]] = load i32, ptr %[[SHOULD_DELETE_VAR]] +// CLANG20: call x86_thiscallcc void @"??1H@operator_delete@@EAE@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]]) +// CLANG20-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 1 +// CLANG20-NEXT: %[[CONDITION:[0-9]+]] = icmp eq i32 %[[AND]], 0 +// CLANG20-NEXT: br i1 %[[CONDITION]], label %[[CONTINUE_LABEL:[0-9a-z._]+]], label %[[CALL_DELETE_LABEL:[0-9a-z._]+]] +// +// CLANG20: [[CALL_DELETE_LABEL]] +// CLANG20-NEXT: call void @"??3H@operator_delete@@CAXPAX@Z"(ptr %[[THIS:[0-9a-z]+]]) +// CLANG20-NEXT: br label %[[CONTINUE_LABEL]] +// +// CLANG20: [[CONTINUE_LABEL]] +// CLANG20-NEXT: %[[RET:.*]] = load ptr, ptr %[[RETVAL]] +// CLANG20-NEXT: ret ptr %[[RET]] +} diff --git a/clang/test/Modules/Inputs/glob-delete-with-virtual-dtor/glob-delete-with-virtual-dtor.h b/clang/test/Modules/Inputs/glob-delete-with-virtual-dtor/glob-delete-with-virtual-dtor.h new file mode 100644 index 0000000000000..405f955f4d50e --- /dev/null +++ b/clang/test/Modules/Inputs/glob-delete-with-virtual-dtor/glob-delete-with-virtual-dtor.h @@ -0,0 +1,18 @@ +class H { + void operator delete(void *); +public: + virtual ~H(); +}; +H::~H() { } + +class S : public H { + void operator delete(void *); +public: + virtual ~S(); +}; +S::~S() { } + +void in_h_tests() { + H* h = new H(); + ::delete h; +} diff --git a/clang/test/Modules/Inputs/glob-delete-with-virtual-dtor/module.modulemap b/clang/test/Modules/Inputs/glob-delete-with-virtual-dtor/module.modulemap new file mode 100644 index 0000000000000..31c1f69b01693 --- /dev/null +++ b/clang/test/Modules/Inputs/glob-delete-with-virtual-dtor/module.modulemap @@ -0,0 +1 @@ +module glob_delete_with_virtual_dtor { header "glob-delete-with-virtual-dtor.h" export * } diff --git a/clang/test/Modules/glob-delete-with-virtual-dtor.cpp b/clang/test/Modules/glob-delete-with-virtual-dtor.cpp new file mode 100644 index 0000000000000..fb2e2a4decf60 --- /dev/null +++ b/clang/test/Modules/glob-delete-with-virtual-dtor.cpp @@ -0,0 +1,44 @@ +// RUN: rm -rf %t +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps %s -x c++ -fmodules-cache-path=%t -I %S/Inputs/glob-delete-with-virtual-dtor -emit-llvm -triple=i386-pc-win32 -o - | FileCheck %s --check-prefixes CHECK,CHECK32 +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps %s -x c++ -fmodules-cache-path=%t -I %S/Inputs/glob-delete-with-virtual-dtor -emit-llvm -triple=x86_64-pc-win32 -o - | FileCheck %s --check-prefixes CHECK,CHECK64 + +#include "glob-delete-with-virtual-dtor.h" + +static void call_in_module_function(void) { + in_h_tests(); +} + +void out_of_module_tests() { + S* s = new S(); + ::delete s; +} + +// CHECK32: define {{.*}} @"??_GH@@UAEPAXI@Z" +// CHECK64: define {{.*}} @"??_GH@@UEAAPEAXI@Z" +// CHECK: store i32 %should_call_delete, ptr %[[SHOULD_DELETE_VAR:[0-9a-z._]+]], align 4 +// CHECK: store ptr %{{.*}}, ptr %[[RETVAL:retval]] +// CHECK: %[[SHOULD_DELETE_VALUE:[0-9a-z._]+]] = load i32, ptr %[[SHOULD_DELETE_VAR]] +// CHECK32: call x86_thiscallcc void @"??1H@@UAE@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]]) +// CHECK64: call void @"??1H@@UEAA@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]]) +// CHECK-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 1 +// CHECK-NEXT: %[[CONDITION:[0-9]+]] = icmp eq i32 %[[AND]], 0 +// CHECK-NEXT: br i1 %[[CONDITION]], label %[[CONTINUE_LABEL:[0-9a-z._]+]], label %[[CALL_DELETE_LABEL:[0-9a-z._]+]] +// +// CHECK: [[CALL_DELETE_LABEL]] +// CHECK-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 4 +// CHECK-NEXT: %[[CONDITION1:[0-9]+]] = icmp eq i32 %[[AND]], 0 +// CHECK-NEXT: br i1 %[[CONDITION1]], label %[[CALL_CLASS_DELETE:[0-9a-z._]+]], label %[[CALL_GLOB_DELETE:[0-9a-z._]+]] +// +// CHECK: [[CALL_GLOB_DELETE]] +// CHECK32-NEXT: call void @"??3@YAXPAXI@Z" +// CHECK64-NEXT: call void @"??3@YAXPEAX_K@Z" +// CHECK-NEXT: br label %[[CONTINUE_LABEL]] +// +// CHECK: [[CALL_CLASS_DELETE]] +// CHECK32-NEXT: call void @"??3H@@CAXPAX@Z" +// CHECK64-NEXT: call void @"??3H@@CAXPEAX@Z" +// CHECK-NEXT: br label %[[CONTINUE_LABEL]] +// +// CHECK: [[CONTINUE_LABEL]] +// CHECK-NEXT: %[[RET:.*]] = load ptr, ptr %[[RETVAL]] +// CHECK-NEXT: ret ptr %[[RET]] diff --git a/clang/test/PCH/Inputs/glob-delete-with-virtual-dtor.h b/clang/test/PCH/Inputs/glob-delete-with-virtual-dtor.h new file mode 100644 index 0000000000000..b5469f014fb38 --- /dev/null +++ b/clang/test/PCH/Inputs/glob-delete-with-virtual-dtor.h @@ -0,0 +1,18 @@ +class H { + void operator delete(void *); +public: + virtual ~H(); +}; +H::~H() { } + +class S : public H { + void operator delete(void *); +public: + virtual ~S(); +}; +S::~S() { } + +void in_pch_tests() { + H* h = new H(); + ::delete h; +} diff --git a/clang/test/PCH/glob-delete-with-virtual-dtor.cpp b/clang/test/PCH/glob-delete-with-virtual-dtor.cpp new file mode 100644 index 0000000000000..29242b04c4a7f --- /dev/null +++ b/clang/test/PCH/glob-delete-with-virtual-dtor.cpp @@ -0,0 +1,47 @@ +// Test this without pch. +// RUN: %clang_cc1 -x c++ -include %S/Inputs/glob-delete-with-virtual-dtor.h -emit-llvm -o - %s + +// Test with pch. +// RUN: %clang_cc1 -x c++ -fno-rtti -emit-pch -o %t -triple=i386-pc-win32 %S/Inputs/glob-delete-with-virtual-dtor.h +// RUN: %clang_cc1 -x c++ -fno-rtti -include-pch %t -emit-llvm -triple=i386-pc-win32 -o - %s | FileCheck %s --check-prefixes CHECK,CHECK32 +// RUN: %clang_cc1 -x c++ -fno-rtti -emit-pch -o %t -triple=x86_64-pc-win32 %S/Inputs/glob-delete-with-virtual-dtor.h +// RUN: %clang_cc1 -x c++ -fno-rtti -include-pch %t -emit-llvm -triple=x86_64-pc-win32 -o - %s | FileCheck %s --check-prefixes CHECK,CHECK64 + +static void call_in_pch_function(void) { + in_pch_tests(); +} + +void out_of_pch_tests() { + S* s = new S(); + ::delete s; +} + +// CHECK32: define {{.*}} @"??_GH@@UAEPAXI@Z" +// CHECK64: define {{.*}} @"??_GH@@UEAAPEAXI@Z" +// CHECK: store i32 %should_call_delete, ptr %[[SHOULD_DELETE_VAR:[0-9a-z._]+]], align 4 +// CHECK: store ptr %{{.*}}, ptr %[[RETVAL:retval]] +// CHECK: %[[SHOULD_DELETE_VALUE:[0-9a-z._]+]] = load i32, ptr %[[SHOULD_DELETE_VAR]] +// CHECK32: call x86_thiscallcc void @"??1H@@UAE@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]]) +// CHECK64: call void @"??1H@@UEAA@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]]) +// CHECK-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 1 +// CHECK-NEXT: %[[CONDITION:[0-9]+]] = icmp eq i32 %[[AND]], 0 +// CHECK-NEXT: br i1 %[[CONDITION]], label %[[CONTINUE_LABEL:[0-9a-z._]+]], label %[[CALL_DELETE_LABEL:[0-9a-z._]+]] +// +// CHECK: [[CALL_DELETE_LABEL]] +// CHECK-NEXT: %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 4 +// CHECK-NEXT: %[[CONDITION1:[0-9]+]] = icmp eq i32 %[[AND]], 0 +// CHECK-NEXT: br i1 %[[CONDITION1]], label %[[CALL_CLASS_DELETE:[0-9a-z._]+]], label %[[CALL_GLOB_DELETE:[0-9a-z._]+]] +// +// CHECK: [[CALL_GLOB_DELETE]] +// CHECK32-NEXT: call void @"??3@YAXPAXI@Z" +// CHECK64-NEXT: call void @"??3@YAXPEAX_K@Z" +// CHECK-NEXT: br label %[[CONTINUE_LABEL]] +// +// CHECK: [[CALL_CLASS_DELETE]] +// CHECK32-NEXT: call void @"??3H@@CAXPAX@Z" +// CHECK64-NEXT: call void @"??3H@@CAXPEAX@Z" +// CHECK-NEXT: br label %[[CONTINUE_LABEL]] +// +// CHECK: [[CONTINUE_LABEL]] +// CHECK-NEXT: %[[RET:.*]] = load ptr, ptr %[[RETVAL]] +// CHECK-NEXT: ret ptr %[[RET]]