Author: Mariya Podchishchaeva
Date: 2025-09-17T10:00:01+02:00
New Revision: c87be722d60467dfc651186c1b1b058ce2e34644

URL: 
https://github.com/llvm/llvm-project/commit/c87be722d60467dfc651186c1b1b058ce2e34644
DIFF: 
https://github.com/llvm/llvm-project/commit/c87be722d60467dfc651186c1b1b058ce2e34644.diff

LOG: [win][clang] Align scalar deleting destructors with MSABI (#139566)

While working on vector deleting destructors support
([GH19772](https://github.com/llvm/llvm-project/issues/19772)), I
noticed that MSVC produces different code in scalar deleting destructor
body depending on whether class defined its own operator delete. In
MSABI deleting destructors accept an additional implicit flag parameter
allowing some sort of flexibility. The mismatch I noticed is that
whenever a global operator delete is called, i.e. `::delete`, in the
code produced by MSVC the implicit flag argument has a value that makes
the 3rd bit set, i.e. "5" for scalar deleting destructors "7" for vector
deleting destructors.
Prior to this patch, clang handled `::delete` via calling global
operator delete direct after the destructor call and not calling class
operator delete in scalar deleting destructor body by passing "0" as
implicit flag argument value. This is fine until there is an attempt to
link binaries compiled with clang with binaries compiled with MSVC. The
problem is that in binaries produced by MSVC the callsite of the
destructor won't call global operator delete because it is assumed that
the destructor will do that and a destructor body generated by clang
will never do.
This patch removes call to global operator delete from the callsite and
add additional check of the 3rd bit of the implicit parameter inside of
scalar deleting destructor body.

---------

Co-authored-by: Tom Honermann <[email protected]>

Added: 
    
clang/test/Modules/Inputs/glob-delete-with-virtual-dtor/glob-delete-with-virtual-dtor.h
    clang/test/Modules/Inputs/glob-delete-with-virtual-dtor/module.modulemap
    clang/test/Modules/glob-delete-with-virtual-dtor.cpp
    clang/test/PCH/Inputs/glob-delete-with-virtual-dtor.h
    clang/test/PCH/glob-delete-with-virtual-dtor.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/AST/ASTMutationListener.h
    clang/include/clang/AST/DeclCXX.h
    clang/include/clang/Basic/ABIVersions.def
    clang/include/clang/Basic/TargetInfo.h
    clang/include/clang/Sema/Sema.h
    clang/include/clang/Serialization/ASTWriter.h
    clang/lib/AST/DeclCXX.cpp
    clang/lib/Basic/TargetInfo.cpp
    clang/lib/CodeGen/CGClass.cpp
    clang/lib/CodeGen/MicrosoftCXXABI.cpp
    clang/lib/Frontend/MultiplexConsumer.cpp
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/lib/Sema/SemaExprCXX.cpp
    clang/lib/Serialization/ASTCommon.h
    clang/lib/Serialization/ASTReaderDecl.cpp
    clang/lib/Serialization/ASTWriter.cpp
    clang/lib/Serialization/ASTWriterDecl.cpp
    clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp
    clang/test/CodeGenCXX/microsoft-abi-structors.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 5211d84be7a3b..518ed9e0f4b3e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -52,6 +52,23 @@ Potentially Breaking Changes
   ``--gcc-install-dir`` command line argument. This will silence the
   warning. It can also be disabled using the
   ``-Wno-gcc-install-dir-libstdcxx`` command line flag.
+- 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 21 or earlier and portions compiled
+  with a version of clang 22 (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 21 and library ``B``
+  is compiled with clang 22, 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. The old behavior is retained under ``-fclang-abi-compat=21``
+  flag.
 
 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 8802664031d37..898487bffec08 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -2873,6 +2873,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,
@@ -2898,11 +2899,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/ABIVersions.def 
b/clang/include/clang/Basic/ABIVersions.def
index f6524bc3bafb9..92edcd830f031 100644
--- a/clang/include/clang/Basic/ABIVersions.def
+++ b/clang/include/clang/Basic/ABIVersions.def
@@ -127,6 +127,14 @@ ABI_VER_MAJOR(19)
 ///   - Incorrectly return C++ records in AVX registers on x86_64.
 ABI_VER_MAJOR(20)
 
+/// Attempt to be ABI-compatible with code generated by Clang 21.0.x.
+/// This causes clang to:
+///   - When targeting Windows emit scalar deleting destructors that are not
+///    compatible with scalar deleting destructors emitted by MSVC for the
+///    cases when the class whose destructor is being emitted defines
+///    operator delete.
+ABI_VER_MAJOR(21)
+
 /// Conform to the underlying platform's C and C++ ABIs as closely as we can.
 ABI_VER_LATEST(Latest)
 

diff  --git a/clang/include/clang/Basic/TargetInfo.h 
b/clang/include/clang/Basic/TargetInfo.h
index 25b68622656fa..e5c5ada3b0858 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -1762,6 +1762,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 41229e8cb644d..d017d1f829015 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -8554,10 +8554,12 @@ class Sema final : public SemaBase {
                                 bool Diagnose = true);
   FunctionDecl *FindUsualDeallocationFunction(SourceLocation StartLoc,
                                               ImplicitDeallocationParameters,
-                                              DeclarationName Name);
+                                              DeclarationName Name,
+                                              bool Diagnose = true);
   FunctionDecl *FindDeallocationFunctionForDestructor(SourceLocation StartLoc,
                                                       CXXRecordDecl *RD,
-                                                      bool Diagnose = true);
+                                                      bool Diagnose,
+                                                      bool LookForGlobal);
 
   /// 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 edf5bbaddf1aa..28c3e55864057 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -949,6 +949,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 aa1f5a1146599..43264f835122f 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3119,6 +3119,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<CXXDestructorDecl>(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 2fbf1ee39b789..72ee09d209e02 100644
--- a/clang/lib/Basic/TargetInfo.cpp
+++ b/clang/lib/Basic/TargetInfo.cpp
@@ -626,6 +626,14 @@ TargetInfo::getCallingConvKind(bool ClangABICompat4) const 
{
   return CCK_Default;
 }
 
+bool TargetInfo::callGlobalDeleteInDeletingDtor(
+    const LangOptions &LangOpts) const {
+  if (getCXXABI() == TargetCXXABI::Microsoft &&
+      LangOpts.getClangABICompat() > LangOptions::ClangABI::Ver21)
+    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 8346ee3aa6a8d..f31f0a2c382d8 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -1599,29 +1599,85 @@ 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<CXXDestructorDecl>(CGF.CurCodeDecl);
+    const CXXRecordDecl *ClassDecl = Dtor->getParent();
+    const FunctionDecl *OD = Dtor->getOperatorDelete();
+    assert(OD->isDestroyingOperatorDelete() == ReturnAfterDelete &&
+           "unexpected value for ReturnAfterDelete");
+    auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+    // MSVC calls global operator delete inside of the dtor body, but clang
+    // aligned with this behavior only after a particular version. This is not
+    // ABI-compatible with previous versions.
+    ASTContext &Context = CGF.getContext();
+    bool CallGlobDelete =
+        Context.getTargetInfo().callGlobalDeleteInDeletingDtor(
+            Context.getLangOpts());
+    if (CallGlobDelete && 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<CXXDestructorDecl>(CGF.CurCodeDecl);
-    const CXXRecordDecl *ClassDecl = Dtor->getParent();
-    CGF.EmitDeleteCall(Dtor->getOperatorDelete(),
-                       LoadThisForDtorDelete(CGF, Dtor),
-                       CGF.getContext().getCanonicalTagType(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.getCanonicalTagType(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<CXXMethodDecl>(OD) && GlobOD && CallGlobDelete) {
+      // 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 94190a149e859..19d9265247119 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -903,12 +903,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) {
@@ -2023,9 +2030,12 @@ llvm::Value *MicrosoftCXXABI::EmitVirtualDestructorCall(
   CGCallee Callee = CGCallee::forVirtual(CE, GD, This, Ty);
 
   ASTContext &Context = getContext();
-  llvm::Value *ImplicitParam = llvm::ConstantInt::get(
-      llvm::IntegerType::getInt32Ty(CGF.getLLVMContext()),
-      DtorType == Dtor_Deleting);
+  bool IsDeleting = DtorType == Dtor_Deleting;
+  bool IsGlobalDelete = D && D->isGlobalDelete() &&
+                        Context.getTargetInfo().callGlobalDeleteInDeletingDtor(
+                            Context.getLangOpts());
+  llvm::Value *ImplicitParam =
+      CGF.Builder.getInt32((IsDeleting ? 1 : 0) | (IsGlobalDelete ? 4 : 0));
 
   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 8008c7b160bed..fb57b43882911 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -11124,8 +11124,8 @@ bool Sema::CheckDestructor(CXXDestructorDecl 
*Destructor) {
       Loc = RD->getLocation();
 
     // If we have a virtual destructor, look up the deallocation function
-    if (FunctionDecl *OperatorDelete =
-            FindDeallocationFunctionForDestructor(Loc, RD)) {
+    if (FunctionDecl *OperatorDelete = FindDeallocationFunctionForDestructor(
+            Loc, RD, /*Diagnose=*/true, /*LookForGlobal=*/false)) {
       Expr *ThisArg = nullptr;
 
       // If the notional 'delete this' expression requires a non-trivial
@@ -11160,6 +11160,22 @@ bool Sema::CheckDestructor(CXXDestructorDecl 
*Destructor) {
       DiagnoseUseOfDecl(OperatorDelete, Loc);
       MarkFunctionReferenced(Loc, OperatorDelete);
       Destructor->setOperatorDelete(OperatorDelete, ThisArg);
+
+      if (isa<CXXMethodDecl>(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 5a9279d928465..1e8bb6e3064a9 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -3579,7 +3579,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);
@@ -3594,7 +3594,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;
@@ -3605,7 +3605,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;
@@ -3614,18 +3615,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 44b8ba6f4c984..4356f2b734fb0 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2339,8 +2339,8 @@ void 
ASTDeclReader::VisitCXXConstructorDecl(CXXConstructorDecl *D) {
 void ASTDeclReader::VisitCXXDestructorDecl(CXXDestructorDecl *D) {
   VisitCXXMethodDecl(D);
 
+  CXXDestructorDecl *Canon = D->getCanonicalDecl();
   if (auto *OperatorDelete = readDeclAs<FunctionDecl>()) {
-    CXXDestructorDecl *Canon = D->getCanonicalDecl();
     auto *ThisArg = Record.readExpr();
     // FIXME: Check consistency if we have an old and new operator delete.
     if (!Canon->OperatorDelete) {
@@ -2348,6 +2348,11 @@ void 
ASTDeclReader::VisitCXXDestructorDecl(CXXDestructorDecl *D) {
       Canon->OperatorDeleteThisArg = ThisArg;
     }
   }
+  if (auto *OperatorGlobDelete = readDeclAs<FunctionDecl>()) {
+    if (!Canon->OperatorGlobalDelete) {
+      Canon->OperatorGlobalDelete = OperatorGlobDelete;
+    }
+  }
 }
 
 void ASTDeclReader::VisitCXXConversionDecl(CXXConversionDecl *D) {
@@ -4846,6 +4851,14 @@ void ASTDeclReader::UpdateDecl(Decl *D) {
       break;
     }
 
+    case DeclUpdateKind::CXXResolvedDtorGlobDelete: {
+      auto *Del = readDeclAs<FunctionDecl>();
+      auto *Canon = cast<CXXDestructorDecl>(D->getCanonicalDecl());
+      if (!Canon->OperatorGlobalDelete)
+        Canon->OperatorGlobalDelete = Del;
+      break;
+    }
+
     case DeclUpdateKind::CXXResolvedExceptionSpec: {
       SmallVector<QualType, 8> ExceptionStorage;
       auto ESI = Record.readExceptionSpecInfo(ExceptionStorage);

diff  --git a/clang/lib/Serialization/ASTWriter.cpp 
b/clang/lib/Serialization/ASTWriter.cpp
index 2aa77934c08d1..3293a54a0a093 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -6528,6 +6528,10 @@ void ASTWriter::WriteDeclUpdatesBlocks(ASTContext 
&Context,
         Record.AddStmt(cast<CXXDestructorDecl>(D)->getOperatorDeleteThisArg());
         break;
 
+      case DeclUpdateKind::CXXResolvedDtorGlobDelete:
+        Record.AddDeclRef(Update.getDecl());
+        break;
+
       case DeclUpdateKind::CXXResolvedExceptionSpec: {
         auto prototype =
           cast<FunctionDecl>(D)->getType()->castAs<FunctionProtoType>();
@@ -7576,6 +7580,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 ec3dda1fcdf48..a8c487005f6ec 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..24b1a4dd42977 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,CLANG22-MSABI,CLANG22-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,CLANG22-MSABI,CLANG22-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,CLANG21-MSABI,CLANG21-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,CLANG21-MSABI,CLANG21-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]]
+// CLANG22-MSABI-SAME: , i32 noundef 5)
+// CLANG21-MSABI-SAME: , i32 noundef 0)
+// CLANG22-MSABI-NOT: call
+// CLANG21-MSABI64: call void @"??3@YAXPEAX_K@Z"({{.*}})
+// CLANG21-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]]
+// CLANG22: %[[THIRDBIT:.*]] = and i32 %[[IMP_PARAM]], 4
+// CLANG22-NEXT: %[[CHCK:.*]] = icmp eq i32 %[[THIRDBIT]], 0
+// CLANG22-NEXT: br i1 %[[CHCK]], label %dtor.entry_cont, label %dtor.call_dtor
+// CLANG21-MSABI: %[[FIRSTBIT:.*]] = and i32 %[[IMP_PARAM]], 1
+// CLANG21-MSABI: %[[CHCK:.*]] = icmp eq i32 %[[FIRSTBIT]], 0
+// CLANG21-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{{ }}
+// CLANG22-MSABI: dtor.call_dtor:
+// CLANG22-MSABI64-NEXT: call void @"??1H@@UEAA@XZ"({{.*}})
+// CLANG22-MSABI32-NEXT: call x86_thiscallcc void @"??1H@@UAE@XZ"({{.*}})
+// CLANG22-MSABI-NEXT: br label %dtor.entry_cont
+//
+// CLANG22-MSABI-LABEL: dtor.entry_cont:
+// CLANG22-MSABI-NEXT: %[[FIRSTBIT:.*]] = and i32 %[[IMP_PARAM]], 1
+// CLANG22-MSABI-NEXT: %[[CHCK1:.*]] = icmp eq i32 %[[FIRSTBIT]], 0
+// CLANG22-MSABI-NEXT: br i1 %[[CHCK1]], label %dtor.continue, label 
%dtor.call_delete
+//
+// CLANG22-MSABI-LABEL: dtor.call_delete:
+// CLANG22-MSABI: %[[THIRDBIT1:.*]] = and i32 %[[IMP_PARAM]], 4
+// CLANG22-MSABI-NEXT: %[[CHCK2:.*]] = icmp eq i32 %[[THIRDBIT1]], 0
+// CLANG22-MSABI-NEXT: br i1 %[[CHCK2]], label %dtor.call_class_delete, label 
%dtor.call_glob_delete
+//
+// CLANG22-MSABI-LABEL: dtor.call_glob_delete:
+// CLANG22-MSABI64: call void @"??3@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 
noundef 48)
+// CLANG22-MSABI32: call void @"??3@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef 
%{{.*}}, i32 noundef 32, i32 noundef 16)
+// CLANG22-MSABI-NEXT: br label %[[RETURN:.*]]
+//
+// CLANG21-MSABI: dtor.call_delete:
+// CLANG22-MSABI: dtor.call_class_delete:
+// CLANG22-MSABI-NOT: call{{ }}
+// CLANG22-MSABI64: getelementptr {{.*}}, i64 24
+// CLANG22-MSABI32: getelementptr {{.*}}, i32 20
+// CLANG22-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
+// CLANG22-MSABI-NEXT: and i32 %[[IMP_PARAM:.*]], 4
+// CLANG22-MSABI-NEXT: icmp eq i32 {{.*}}, 0
+// CLANG22-MSABI-NEXT: br i1 %{{.*}}, label %dtor.entry_cont, label 
%dtor.call_dtor
+//
+// CLANG22-MSABI: dtor.call_dtor:
+// CLANG22-MSABI64-NEXT: call void @"??1I@@UEAA@XZ"({{.*}})
+// CLANG22-MSABI32-NEXT: call x86_thiscallcc void @"??1I@@UAE@XZ"({{.*}})
+// CLANG22-MSABI-NEXT: br label %dtor.entry_cont
+//
+// CLANG22-MSABI: dtor.entry_cont:
+// CLANG22-MSABI-NEXT: and i32 %[[IMP_PARAM]], 1
+// CLANG22-MSABI-NEXT: icmp eq i32 %{{.*}}, 0
+// CLANG22-MSABI-NEXT: br i1 %{{.*}}, label %dtor.continue, label 
%dtor.call_delete
+//
+// CLANG22-MSABI: dtor.call_delete:
+// CLANG22-MSABI-NEXT: %[[THIRDBIT1:.*]] = and i32 %[[IMP_PARAM]], 4
+// CLANG22-MSABI-NEXT: %[[CHCK2:.*]] = icmp eq i32 %[[THIRDBIT1]], 0
+// CLANG22-MSABI-NEXT: br i1 %[[CHCK2]], label %dtor.call_class_delete, label 
%dtor.call_glob_delete
+//
+// CLANG22-MSABI: dtor.call_glob_delete:
+// CLANG22-MSABI64: call void @"??3@YAXPEAX_KW4align_val_t@std@@@Z"(ptr 
noundef %{{.*}}, i64 noundef 96, i64 noundef 32)
+// CLANG22-MSABI32: call void @"??3@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef 
%{{.*}}, i32 noundef 64, i32 noundef 32)
+// CLANG22-MSABI-NEXT: br label %[[RETURN:.*]]
 //
+// CLANG21-MSABI: dtor.call_delete:
+// CLANG22-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..497775840e049 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,CLANG22 %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,CLANG21 %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]])
+// CLANG22-NEXT:   %[[CALL:.*]] = call x86_thiscallcc ptr %[[VDTOR]](ptr 
{{[^,]*}} %[[OBJ_PTR_VALUE]], i32 5)
+// CLANG21-NEXT:   %[[CALL:.*]] = call x86_thiscallcc ptr %[[VDTOR]](ptr 
{{[^,]*}} %[[OBJ_PTR_VALUE]], i32 0)
+// CLANG21-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]]
+
+// CLANG21:      define linkonce_odr dso_local x86_thiscallcc ptr 
@"??_GH@operator_delete@@EAEPAXI@Z"(ptr {{[^,]*}} %this, i32 
%should_call_delete) {{.*}} comdat {{.*}} {
+// CLANG21:        store i32 %should_call_delete, ptr 
%[[SHOULD_DELETE_VAR:[0-9a-z._]+]], align 4
+// CLANG21:        store ptr %{{.*}}, ptr %[[RETVAL:retval]]
+// CLANG21:        %[[SHOULD_DELETE_VALUE:[0-9a-z._]+]] = load i32, ptr 
%[[SHOULD_DELETE_VAR]]
+// CLANG21:        call x86_thiscallcc void 
@"??1H@operator_delete@@EAE@XZ"(ptr {{[^,]*}} %[[THIS:[0-9a-z]+]])
+// CLANG21-NEXT:   %[[AND:[0-9]+]] = and i32 %[[SHOULD_DELETE_VALUE]], 1
+// CLANG21-NEXT:   %[[CONDITION:[0-9]+]] = icmp eq i32 %[[AND]], 0
+// CLANG21-NEXT:   br i1 %[[CONDITION]], label 
%[[CONTINUE_LABEL:[0-9a-z._]+]], label %[[CALL_DELETE_LABEL:[0-9a-z._]+]]
+//
+// CLANG21:      [[CALL_DELETE_LABEL]]
+// CLANG21-NEXT:   call void @"??3H@operator_delete@@CAXPAX@Z"(ptr 
%[[THIS:[0-9a-z]+]])
+// CLANG21-NEXT:   br label %[[CONTINUE_LABEL]]
+//
+// CLANG21:      [[CONTINUE_LABEL]]
+// CLANG21-NEXT:   %[[RET:.*]] = load ptr, ptr %[[RETVAL]]
+// CLANG21-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]]


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to