Skip to content

[MS][clang] Add support for vector deleting destructors #126240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 4, 2025

Conversation

Fznamznon
Copy link
Contributor

Whereas it is UB in terms of the standard to delete an array of objects via pointer whose static type doesn't match its dynamic type, MSVC supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case, currently not having this extension implemented causes clang to generate code that is not compatible with the code generated by MSVC, because clang always puts scalar deleting destructor to the vftable. This PR aims to resolve these problems.

Fixes #19772

Whereas it is UB in terms of the standard to delete an array of objects via
pointer whose static type doesn't match its dynamic type, MSVC supports
an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case, currently
not having this extension implemented causes clang to generate code that is not
compatible with the code generated by MSVC, because clang always
puts scalar deleting destructor to the vftable. This PR aims to resolve
these problems.

Fixes llvm#19772
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:codegen IR generation bugs: mangling, exceptions, etc. debuginfo labels Feb 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Feb 7, 2025

@llvm/pr-subscribers-clang-modules
@llvm/pr-subscribers-debuginfo

@llvm/pr-subscribers-clang-codegen

Author: Mariya Podchishchaeva (Fznamznon)

Changes

Whereas it is UB in terms of the standard to delete an array of objects via pointer whose static type doesn't match its dynamic type, MSVC supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case, currently not having this extension implemented causes clang to generate code that is not compatible with the code generated by MSVC, because clang always puts scalar deleting destructor to the vftable. This PR aims to resolve these problems.

Fixes #19772


Patch is 78.11 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/126240.diff

33 Files Affected:

  • (modified) clang/include/clang/AST/VTableBuilder.h (+4-2)
  • (modified) clang/include/clang/Basic/ABI.h (+5-4)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+2)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+10-8)
  • (modified) clang/lib/AST/VTableBuilder.cpp (+12-6)
  • (modified) clang/lib/CodeGen/CGCXX.cpp (+36-1)
  • (modified) clang/lib/CodeGen/CGCXXABI.cpp (+14)
  • (modified) clang/lib/CodeGen/CGCXXABI.h (+15-8)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+87-10)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+45-5)
  • (modified) clang/lib/CodeGen/CGVTables.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+35)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+6)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+18-9)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+49-24)
  • (modified) clang/test/CodeGenCXX/debug-info-windows-dtor.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/dllexport.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-extern-template.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-structors.cpp (+3-2)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-thunks.cpp (+1-2)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vftables.cpp (+10-10)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-virtual-inheritance.cpp (+9-8)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-multiple-nonvirtual-inheritance-vdtors.cpp (+9-9)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-return-thunks.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-single-inheritance.cpp (+10-10)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-virtual-inheritance-vtordisps.cpp (+15-15)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-virtual-inheritance.cpp (+9-9)
  • (modified) clang/test/CodeGenCXX/microsoft-no-rtti-data.cpp (+1-1)
  • (added) clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp (+108)
  • (modified) clang/test/CodeGenCXX/vtable-consteval.cpp (+2-2)
  • (modified) clang/test/Modules/vtable-windows.cppm (+1-1)
  • (modified) clang/test/Profile/cxx-abc-deleting-dtor.cpp (+4-5)
diff --git a/clang/include/clang/AST/VTableBuilder.h b/clang/include/clang/AST/VTableBuilder.h
index a5de41dbc22f14b..e1efe8cddcc5e58 100644
--- a/clang/include/clang/AST/VTableBuilder.h
+++ b/clang/include/clang/AST/VTableBuilder.h
@@ -150,7 +150,7 @@ class VTableComponent {
 
   bool isRTTIKind() const { return isRTTIKind(getKind()); }
 
-  GlobalDecl getGlobalDecl() const {
+  GlobalDecl getGlobalDecl(bool HasVectorDeletingDtors) const {
     assert(isUsedFunctionPointerKind() &&
            "GlobalDecl can be created only from virtual function");
 
@@ -161,7 +161,9 @@ class VTableComponent {
     case CK_CompleteDtorPointer:
       return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Complete);
     case CK_DeletingDtorPointer:
-      return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Deleting);
+      return GlobalDecl(DtorDecl, (HasVectorDeletingDtors)
+                                      ? CXXDtorType::Dtor_VectorDeleting
+                                      : CXXDtorType::Dtor_Deleting);
     case CK_VCallOffset:
     case CK_VBaseOffset:
     case CK_OffsetToTop:
diff --git a/clang/include/clang/Basic/ABI.h b/clang/include/clang/Basic/ABI.h
index 231bad799a42cbd..48969e4f295c364 100644
--- a/clang/include/clang/Basic/ABI.h
+++ b/clang/include/clang/Basic/ABI.h
@@ -31,10 +31,11 @@ enum CXXCtorType {
 
 /// C++ destructor types.
 enum CXXDtorType {
-    Dtor_Deleting, ///< Deleting dtor
-    Dtor_Complete, ///< Complete object dtor
-    Dtor_Base,     ///< Base object dtor
-    Dtor_Comdat    ///< The COMDAT used for dtors
+  Dtor_Deleting,      ///< Deleting dtor
+  Dtor_Complete,      ///< Complete object dtor
+  Dtor_Base,          ///< Base object dtor
+  Dtor_Comdat,        ///< The COMDAT used for dtors
+  Dtor_VectorDeleting ///< Vector deleting dtor
 };
 
 } // end namespace clang
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index e5eb22eae7dd139..3209c1777fc7f4b 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6001,6 +6001,8 @@ void CXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   case Dtor_Comdat:
     Out << "D5";
     break;
+  case Dtor_VectorDeleting:
+    llvm_unreachable("Itanium ABI does not use vector deleting dtors");
   }
 }
 
diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp
index fe34251688a9888..d5dc7833a7fcc43 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -1484,8 +1484,7 @@ void MicrosoftCXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   // <operator-name> ::= ?_G # scalar deleting destructor
   case Dtor_Deleting: Out << "?_G"; return;
   // <operator-name> ::= ?_E # vector deleting destructor
-  // FIXME: Add a vector deleting dtor type.  It goes in the vtable, so we need
-  // it.
+  case Dtor_VectorDeleting: Out << "?_E"; return;
   case Dtor_Comdat:
     llvm_unreachable("not expecting a COMDAT");
   }
@@ -2886,9 +2885,12 @@ void MicrosoftCXXNameMangler::mangleFunctionType(const FunctionType *T,
   //               ::= @ # structors (they have no declared return type)
   if (IsStructor) {
     if (isa<CXXDestructorDecl>(D) && isStructorDecl(D)) {
-      // The scalar deleting destructor takes an extra int argument which is not
+      // The deleting destructors take an extra argument of type int that indicates
+      // whether the storage for the object should be deleted and whether a single
+      // object or an array of objects is being destroyed. This extra argument is not
       // reflected in the AST.
-      if (StructorType == Dtor_Deleting) {
+      if (StructorType == Dtor_Deleting ||
+          StructorType == Dtor_VectorDeleting) {
         Out << (PointersAre64Bit ? "PEAXI@Z" : "PAXI@Z");
         return;
       }
@@ -3861,10 +3863,10 @@ void MicrosoftMangleContextImpl::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
                                                     const ThunkInfo &Thunk,
                                                     bool /*ElideOverrideInfo*/,
                                                     raw_ostream &Out) {
-  // FIXME: Actually, the dtor thunk should be emitted for vector deleting
-  // dtors rather than scalar deleting dtors. Just use the vector deleting dtor
-  // mangling manually until we support both deleting dtor types.
-  assert(Type == Dtor_Deleting);
+  // The dtor thunk should use vector deleting dtor mangling, however as an
+  // optimization we may end up emitting only scalar deleting dtor body, so just
+  // use the vector deleting dtor mangling manually.
+  assert(Type == Dtor_Deleting || Type == Dtor_VectorDeleting);
   msvc_hashing_ostream MHO(Out);
   MicrosoftCXXNameMangler Mangler(*this, MHO, DD, Type);
   Mangler.getStream() << "??_E";
diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp
index fa3055dd1206f41..9b7f72e2390f445 100644
--- a/clang/lib/AST/VTableBuilder.cpp
+++ b/clang/lib/AST/VTableBuilder.cpp
@@ -1733,8 +1733,8 @@ void ItaniumVTableBuilder::LayoutPrimaryAndSecondaryVTables(
       const CXXMethodDecl *MD = I.first;
       const MethodInfo &MI = I.second;
       if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-        MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)]
-            = MI.VTableIndex - AddressPoint;
+        MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)] =
+            MI.VTableIndex - AddressPoint;
         MethodVTableIndices[GlobalDecl(DD, Dtor_Deleting)]
             = MI.VTableIndex + 1 - AddressPoint;
       } else {
@@ -2655,7 +2655,10 @@ class VFTableBuilder {
       MethodVFTableLocation Loc(MI.VBTableIndex, WhichVFPtr.getVBaseWithVPtr(),
                                 WhichVFPtr.NonVirtualOffset, MI.VFTableIndex);
       if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-        MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
+        if (!Context.getTargetInfo().getCXXABI().isMicrosoft())
+          MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
+        else
+          MethodVFTableLocations[GlobalDecl(DD, Dtor_VectorDeleting)] = Loc;
       } else {
         MethodVFTableLocations[MD] = Loc;
       }
@@ -3285,7 +3288,10 @@ void VFTableBuilder::dumpLayout(raw_ostream &Out) {
       const CXXDestructorDecl *DD = Component.getDestructorDecl();
 
       DD->printQualifiedName(Out);
-      Out << "() [scalar deleting]";
+      if (Context.getTargetInfo().getCXXABI().isMicrosoft())
+        Out << "() [vector deleting]";
+      else
+        Out << "() [scalar deleting]";
 
       if (DD->isPureVirtual())
         Out << " [pure]";
@@ -3756,7 +3762,7 @@ void MicrosoftVTableContext::dumpMethodLocations(
         PredefinedIdentKind::PrettyFunctionNoVirtual, MD);
 
     if (isa<CXXDestructorDecl>(MD)) {
-      IndicesMap[I.second] = MethodName + " [scalar deleting]";
+      IndicesMap[I.second] = MethodName + " [vector deleting]";
     } else {
       IndicesMap[I.second] = MethodName;
     }
@@ -3873,7 +3879,7 @@ MicrosoftVTableContext::getMethodVFTableLocation(GlobalDecl GD) {
   assert(hasVtableSlot(cast<CXXMethodDecl>(GD.getDecl())) &&
          "Only use this method for virtual methods or dtors");
   if (isa<CXXDestructorDecl>(GD.getDecl()))
-    assert(GD.getDtorType() == Dtor_Deleting);
+    assert(GD.getDtorType() == Dtor_VectorDeleting);
 
   GD = GD.getCanonicalDecl();
 
diff --git a/clang/lib/CodeGen/CGCXX.cpp b/clang/lib/CodeGen/CGCXX.cpp
index 78a7b021855b7e8..6f47e24eed5b374 100644
--- a/clang/lib/CodeGen/CGCXX.cpp
+++ b/clang/lib/CodeGen/CGCXX.cpp
@@ -175,7 +175,6 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
   // requires explicit comdat support in the IL.
   if (llvm::GlobalValue::isWeakForLinker(TargetLinkage))
     return true;
-
   // Create the alias with no name.
   auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
                                           Aliasee, &getModule());
@@ -201,6 +200,42 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
   return false;
 }
 
+/// Emit a definition as a global alias for another definition, unconditionally.
+void CodeGenModule::EmitDefinitionAsAlias(GlobalDecl AliasDecl,
+                                          GlobalDecl TargetDecl) {
+
+  llvm::Type *AliasValueType = getTypes().GetFunctionType(AliasDecl);
+
+  StringRef MangledName = getMangledName(AliasDecl);
+  llvm::GlobalValue *Entry = GetGlobalValue(MangledName);
+  if (Entry && !Entry->isDeclaration())
+    return;
+  auto *Aliasee = cast<llvm::GlobalValue>(GetAddrOfGlobal(TargetDecl));
+
+  // Determine the linkage type for the alias.
+  llvm::GlobalValue::LinkageTypes Linkage = getFunctionLinkage(AliasDecl);
+
+  // Create the alias with no name.
+  auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
+                                          Aliasee, &getModule());
+  // Destructors are always unnamed_addr.
+  Alias->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+
+  if (Entry) {
+    assert(Entry->getValueType() == AliasValueType &&
+           Entry->getAddressSpace() == Alias->getAddressSpace() &&
+           "declaration exists with different type");
+    Alias->takeName(Entry);
+    Entry->replaceAllUsesWith(Alias);
+    Entry->eraseFromParent();
+  } else {
+    Alias->setName(MangledName);
+  }
+
+  // Set any additional necessary attributes for the alias.
+  SetCommonAttributes(AliasDecl, Alias);
+}
+
 llvm::Function *CodeGenModule::codegenCXXStructor(GlobalDecl GD) {
   const CGFunctionInfo &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD);
   auto *Fn = cast<llvm::Function>(
diff --git a/clang/lib/CodeGen/CGCXXABI.cpp b/clang/lib/CodeGen/CGCXXABI.cpp
index 7c6dfc3e59d8c0e..e109fd0c443f6a0 100644
--- a/clang/lib/CodeGen/CGCXXABI.cpp
+++ b/clang/lib/CodeGen/CGCXXABI.cpp
@@ -273,6 +273,20 @@ void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
   numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
 }
 
+void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
+                               QualType eltTy, llvm::Value *&numElements,
+                               llvm::Value *&allocPtr, CharUnits &cookieSize) {
+  assert(eltTy.isDestructedType());
+
+  // Derive a char* in the same address space as the pointer.
+  ptr = ptr.withElementType(CGF.Int8Ty);
+
+  cookieSize = getArrayCookieSizeImpl(eltTy);
+  Address allocAddr = CGF.Builder.CreateConstInBoundsByteGEP(ptr, -cookieSize);
+  allocPtr = allocAddr.emitRawPointer(CGF);
+  numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
+}
+
 llvm::Value *CGCXXABI::readArrayCookieImpl(CodeGenFunction &CGF,
                                            Address ptr,
                                            CharUnits cookieSize) {
diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h
index 687ff7fb844445a..fbbe8f6c6142cb7 100644
--- a/clang/lib/CodeGen/CGCXXABI.h
+++ b/clang/lib/CodeGen/CGCXXABI.h
@@ -251,9 +251,10 @@ class CGCXXABI {
 
 public:
   virtual void emitVirtualObjectDelete(CodeGenFunction &CGF,
-                                       const CXXDeleteExpr *DE,
-                                       Address Ptr, QualType ElementType,
-                                       const CXXDestructorDecl *Dtor) = 0;
+                                       const CXXDeleteExpr *DE, Address Ptr,
+                                       QualType ElementType,
+                                       const CXXDestructorDecl *Dtor,
+                                       bool ArrayDeletion) = 0;
   virtual void emitRethrow(CodeGenFunction &CGF, bool isNoReturn) = 0;
   virtual void emitThrow(CodeGenFunction &CGF, const CXXThrowExpr *E) = 0;
   virtual llvm::GlobalVariable *getThrowInfo(QualType T) { return nullptr; }
@@ -275,6 +276,7 @@ class CGCXXABI {
   virtual CatchTypeInfo getCatchAllTypeInfo();
 
   virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
+  virtual bool hasVectorDeletingDtors() = 0;
   virtual void EmitBadTypeidCall(CodeGenFunction &CGF) = 0;
   virtual llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
                                   Address ThisPtr,
@@ -485,11 +487,10 @@ class CGCXXABI {
       llvm::PointerUnion<const CXXDeleteExpr *, const CXXMemberCallExpr *>;
 
   /// Emit the ABI-specific virtual destructor call.
-  virtual llvm::Value *
-  EmitVirtualDestructorCall(CodeGenFunction &CGF, const CXXDestructorDecl *Dtor,
-                            CXXDtorType DtorType, Address This,
-                            DeleteOrMemberCallExpr E,
-                            llvm::CallBase **CallOrInvoke) = 0;
+  virtual llvm::Value *EmitVirtualDestructorCall(
+      CodeGenFunction &CGF, const CXXDestructorDecl *Dtor, CXXDtorType DtorType,
+      Address This, DeleteOrMemberCallExpr E, llvm::CallBase **CallOrInvoke,
+      bool ArrayDeletion = false) = 0;
 
   virtual void adjustCallArgsForDestructorThunk(CodeGenFunction &CGF,
                                                 GlobalDecl GD,
@@ -575,6 +576,12 @@ class CGCXXABI {
                                QualType ElementType, llvm::Value *&NumElements,
                                llvm::Value *&AllocPtr, CharUnits &CookieSize);
 
+  /// Reads the array cookie associated with the given pointer,
+  /// that should have one.
+  virtual void ReadArrayCookie(CodeGenFunction &CGF, Address Ptr,
+                               QualType ElementType, llvm::Value *&NumElements,
+                               llvm::Value *&AllocPtr, CharUnits &CookieSize);
+
   /// Return whether the given global decl needs a VTT parameter.
   virtual bool NeedsVTTParameter(GlobalDecl GD);
 
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index 7a1096fcbca822b..bb94d502c34faf1 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -1433,6 +1433,83 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF,
   return true;
 }
 
+namespace {
+llvm::Value *LoadThisForDtorDelete(CodeGenFunction &CGF,
+                                   const CXXDestructorDecl *DD) {
+  if (Expr *ThisArg = DD->getOperatorDeleteThisArg())
+    return CGF.EmitScalarExpr(ThisArg);
+  return CGF.LoadCXXThis();
+}
+}
+
+void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD,
+                                  CodeGenFunction &CGF,
+                                  llvm::Value *ShouldDeleteCondition) {
+  Address ThisPtr = CGF.LoadCXXThisAddress();
+  llvm::BasicBlock *ScalarBB = CGF.createBasicBlock("dtor.scalar");
+  llvm::BasicBlock *callDeleteBB =
+      CGF.createBasicBlock("dtor.call_delete_after_array_destroy");
+  llvm::BasicBlock *VectorBB = CGF.createBasicBlock("dtor.vector");
+  auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+  llvm::Value *CheckTheBitForArrayDestroy = CGF.Builder.CreateAnd(
+      ShouldDeleteCondition,
+      llvm::Constant::getIntegerValue(CondTy, llvm::APInt(CondTy->getBitWidth(),
+                                                          /*val=*/2)));
+  llvm::Value *ShouldDestroyArray =
+      CGF.Builder.CreateIsNull(CheckTheBitForArrayDestroy);
+  CGF.Builder.CreateCondBr(ShouldDestroyArray, ScalarBB, VectorBB);
+
+  CGF.EmitBlock(VectorBB);
+
+  llvm::Value *numElements = nullptr;
+  llvm::Value *allocatedPtr = nullptr;
+  CharUnits cookieSize;
+  QualType EltTy = DD->getThisType()->getPointeeType();
+  CGF.CGM.getCXXABI().ReadArrayCookie(CGF, ThisPtr, EltTy, numElements,
+                                      allocatedPtr, cookieSize);
+
+  // Destroy the elements.
+  QualType::DestructionKind dtorKind = EltTy.isDestructedType();
+
+  assert(dtorKind);
+  assert(numElements && "no element count for a type with a destructor!");
+
+  CharUnits elementSize = CGF.getContext().getTypeSizeInChars(EltTy);
+  CharUnits elementAlign =
+      ThisPtr.getAlignment().alignmentOfArrayElement(elementSize);
+
+  llvm::Value *arrayBegin = ThisPtr.emitRawPointer(CGF);
+  llvm::Value *arrayEnd = CGF.Builder.CreateInBoundsGEP(
+      ThisPtr.getElementType(), arrayBegin, numElements, "delete.end");
+
+  // We already checked that the array is not 0-length before entering vector
+  // deleting dtor.
+  CGF.emitArrayDestroy(arrayBegin, arrayEnd, EltTy, elementAlign,
+                       CGF.getDestroyer(dtorKind),
+                       /*checkZeroLength*/ false, CGF.needsEHCleanup(dtorKind));
+
+  llvm::BasicBlock *VectorBBCont = CGF.createBasicBlock("dtor.vector.cont");
+  CGF.EmitBlock(VectorBBCont);
+
+  llvm::Value *CheckTheBitForDeleteCall = CGF.Builder.CreateAnd(
+      ShouldDeleteCondition,
+      llvm::Constant::getIntegerValue(CondTy, llvm::APInt(CondTy->getBitWidth(),
+                                                          /*val=*/1)));
+
+  llvm::Value *ShouldCallDelete =
+      CGF.Builder.CreateIsNull(CheckTheBitForDeleteCall);
+  CGF.Builder.CreateCondBr(ShouldCallDelete, CGF.ReturnBlock.getBlock(),
+                           callDeleteBB);
+  CGF.EmitBlock(callDeleteBB);
+  const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
+  const CXXRecordDecl *ClassDecl = Dtor->getParent();
+  CGF.EmitDeleteCall(Dtor->getOperatorDelete(), allocatedPtr,
+                     CGF.getContext().getTagDeclType(ClassDecl));
+
+  CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
+  CGF.EmitBlock(ScalarBB);
+}
+
 /// EmitDestructorBody - Emits the body of the current destructor.
 void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CurGD.getDecl());
@@ -1462,7 +1539,9 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   // outside of the function-try-block, which means it's always
   // possible to delegate the destructor body to the complete
   // destructor.  Do so.
-  if (DtorType == Dtor_Deleting) {
+  if (DtorType == Dtor_Deleting || DtorType == Dtor_VectorDeleting) {
+    if (CXXStructorImplicitParamValue && DtorType == Dtor_VectorDeleting)
+      EmitConditionalArrayDtorCall(Dtor, *this, CXXStructorImplicitParamValue);
     RunCleanupsScope DtorEpilogue(*this);
     EnterDtorCleanups(Dtor, Dtor_Deleting);
     if (HaveInsertPoint()) {
@@ -1491,6 +1570,7 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   switch (DtorType) {
   case Dtor_Comdat: llvm_unreachable("not expecting a COMDAT");
   case Dtor_Deleting: llvm_unreachable("already handled deleting case");
+  case Dtor_VectorDeleting: llvm_unreachable("already handled vector deleting case");
 
   case Dtor_Complete:
     assert((Body || getTarget().getCXXABI().isMicrosoft()) &&
@@ -1567,13 +1647,6 @@ void CodeGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &Args)
 }
 
 namespace {
-  llvm::Value *LoadThisForDtorDelete(CodeGenFunction &CGF,
-                                     const CXXDestructorDecl *DD) {
-    if (Expr *ThisArg = DD->getOperatorDeleteThisArg())
-      return CGF.EmitScalarExpr(ThisArg);
-    return CGF.LoadCXXThis();
-  }
-
   /// Call the operator delete associated with the current destructor.
   struct CallDtorDelete final : EHScopeStack::Cleanup {
     CallDtorDelete() {}
@@ -1592,8 +1665,12 @@ namespace {
                                      bool ReturnAfterDelete) {
     llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete");
     llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue");
-    llvm::Value *ShouldCallDelete
-      = CGF.Builder.CreateIsNull(ShouldDeleteCondition);
+    auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+    llvm::Value *CheckTheBit = CGF.Builder.CreateAnd(
+        ShouldDeleteCondition, llvm::Constant::getIntegerValue(
+                                   CondTy, llvm::APInt(CondTy->getBitWidth(),
+                                                       /*val=*/1)));
+    llvm::Value *ShouldCallDelete = CGF.Builder.CreateIsNull(CheckTheBit);
     CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB);
 
     CGF.EmitBlock(callDeleteBB);
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index d5b584ec0f2e95b..da514ff612a32ef 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -2104,7 +2104,8 @@ llvm::DISubprogram *CGDebugInfo::CreateCXXMemberFunction(
       // Emit MS ABI vftable inf...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Feb 7, 2025

@llvm/pr-subscribers-clang

Author: Mariya Podchishchaeva (Fznamznon)

Changes

Whereas it is UB in terms of the standard to delete an array of objects via pointer whose static type doesn't match its dynamic type, MSVC supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case, currently not having this extension implemented causes clang to generate code that is not compatible with the code generated by MSVC, because clang always puts scalar deleting destructor to the vftable. This PR aims to resolve these problems.

Fixes #19772


Patch is 78.11 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/126240.diff

33 Files Affected:

  • (modified) clang/include/clang/AST/VTableBuilder.h (+4-2)
  • (modified) clang/include/clang/Basic/ABI.h (+5-4)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+2)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+10-8)
  • (modified) clang/lib/AST/VTableBuilder.cpp (+12-6)
  • (modified) clang/lib/CodeGen/CGCXX.cpp (+36-1)
  • (modified) clang/lib/CodeGen/CGCXXABI.cpp (+14)
  • (modified) clang/lib/CodeGen/CGCXXABI.h (+15-8)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+87-10)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CGExprCXX.cpp (+45-5)
  • (modified) clang/lib/CodeGen/CGVTables.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+35)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+6)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+18-9)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+49-24)
  • (modified) clang/test/CodeGenCXX/debug-info-windows-dtor.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/dllexport.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-extern-template.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-structors.cpp (+3-2)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-thunks.cpp (+1-2)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vftables.cpp (+10-10)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-virtual-inheritance.cpp (+9-8)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-multiple-nonvirtual-inheritance-vdtors.cpp (+9-9)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-return-thunks.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-single-inheritance.cpp (+10-10)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-virtual-inheritance-vtordisps.cpp (+15-15)
  • (modified) clang/test/CodeGenCXX/microsoft-abi-vtables-virtual-inheritance.cpp (+9-9)
  • (modified) clang/test/CodeGenCXX/microsoft-no-rtti-data.cpp (+1-1)
  • (added) clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp (+108)
  • (modified) clang/test/CodeGenCXX/vtable-consteval.cpp (+2-2)
  • (modified) clang/test/Modules/vtable-windows.cppm (+1-1)
  • (modified) clang/test/Profile/cxx-abc-deleting-dtor.cpp (+4-5)
diff --git a/clang/include/clang/AST/VTableBuilder.h b/clang/include/clang/AST/VTableBuilder.h
index a5de41dbc22f14b..e1efe8cddcc5e58 100644
--- a/clang/include/clang/AST/VTableBuilder.h
+++ b/clang/include/clang/AST/VTableBuilder.h
@@ -150,7 +150,7 @@ class VTableComponent {
 
   bool isRTTIKind() const { return isRTTIKind(getKind()); }
 
-  GlobalDecl getGlobalDecl() const {
+  GlobalDecl getGlobalDecl(bool HasVectorDeletingDtors) const {
     assert(isUsedFunctionPointerKind() &&
            "GlobalDecl can be created only from virtual function");
 
@@ -161,7 +161,9 @@ class VTableComponent {
     case CK_CompleteDtorPointer:
       return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Complete);
     case CK_DeletingDtorPointer:
-      return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Deleting);
+      return GlobalDecl(DtorDecl, (HasVectorDeletingDtors)
+                                      ? CXXDtorType::Dtor_VectorDeleting
+                                      : CXXDtorType::Dtor_Deleting);
     case CK_VCallOffset:
     case CK_VBaseOffset:
     case CK_OffsetToTop:
diff --git a/clang/include/clang/Basic/ABI.h b/clang/include/clang/Basic/ABI.h
index 231bad799a42cbd..48969e4f295c364 100644
--- a/clang/include/clang/Basic/ABI.h
+++ b/clang/include/clang/Basic/ABI.h
@@ -31,10 +31,11 @@ enum CXXCtorType {
 
 /// C++ destructor types.
 enum CXXDtorType {
-    Dtor_Deleting, ///< Deleting dtor
-    Dtor_Complete, ///< Complete object dtor
-    Dtor_Base,     ///< Base object dtor
-    Dtor_Comdat    ///< The COMDAT used for dtors
+  Dtor_Deleting,      ///< Deleting dtor
+  Dtor_Complete,      ///< Complete object dtor
+  Dtor_Base,          ///< Base object dtor
+  Dtor_Comdat,        ///< The COMDAT used for dtors
+  Dtor_VectorDeleting ///< Vector deleting dtor
 };
 
 } // end namespace clang
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index e5eb22eae7dd139..3209c1777fc7f4b 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -6001,6 +6001,8 @@ void CXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   case Dtor_Comdat:
     Out << "D5";
     break;
+  case Dtor_VectorDeleting:
+    llvm_unreachable("Itanium ABI does not use vector deleting dtors");
   }
 }
 
diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp
index fe34251688a9888..d5dc7833a7fcc43 100644
--- a/clang/lib/AST/MicrosoftMangle.cpp
+++ b/clang/lib/AST/MicrosoftMangle.cpp
@@ -1484,8 +1484,7 @@ void MicrosoftCXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
   // <operator-name> ::= ?_G # scalar deleting destructor
   case Dtor_Deleting: Out << "?_G"; return;
   // <operator-name> ::= ?_E # vector deleting destructor
-  // FIXME: Add a vector deleting dtor type.  It goes in the vtable, so we need
-  // it.
+  case Dtor_VectorDeleting: Out << "?_E"; return;
   case Dtor_Comdat:
     llvm_unreachable("not expecting a COMDAT");
   }
@@ -2886,9 +2885,12 @@ void MicrosoftCXXNameMangler::mangleFunctionType(const FunctionType *T,
   //               ::= @ # structors (they have no declared return type)
   if (IsStructor) {
     if (isa<CXXDestructorDecl>(D) && isStructorDecl(D)) {
-      // The scalar deleting destructor takes an extra int argument which is not
+      // The deleting destructors take an extra argument of type int that indicates
+      // whether the storage for the object should be deleted and whether a single
+      // object or an array of objects is being destroyed. This extra argument is not
       // reflected in the AST.
-      if (StructorType == Dtor_Deleting) {
+      if (StructorType == Dtor_Deleting ||
+          StructorType == Dtor_VectorDeleting) {
         Out << (PointersAre64Bit ? "PEAXI@Z" : "PAXI@Z");
         return;
       }
@@ -3861,10 +3863,10 @@ void MicrosoftMangleContextImpl::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
                                                     const ThunkInfo &Thunk,
                                                     bool /*ElideOverrideInfo*/,
                                                     raw_ostream &Out) {
-  // FIXME: Actually, the dtor thunk should be emitted for vector deleting
-  // dtors rather than scalar deleting dtors. Just use the vector deleting dtor
-  // mangling manually until we support both deleting dtor types.
-  assert(Type == Dtor_Deleting);
+  // The dtor thunk should use vector deleting dtor mangling, however as an
+  // optimization we may end up emitting only scalar deleting dtor body, so just
+  // use the vector deleting dtor mangling manually.
+  assert(Type == Dtor_Deleting || Type == Dtor_VectorDeleting);
   msvc_hashing_ostream MHO(Out);
   MicrosoftCXXNameMangler Mangler(*this, MHO, DD, Type);
   Mangler.getStream() << "??_E";
diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp
index fa3055dd1206f41..9b7f72e2390f445 100644
--- a/clang/lib/AST/VTableBuilder.cpp
+++ b/clang/lib/AST/VTableBuilder.cpp
@@ -1733,8 +1733,8 @@ void ItaniumVTableBuilder::LayoutPrimaryAndSecondaryVTables(
       const CXXMethodDecl *MD = I.first;
       const MethodInfo &MI = I.second;
       if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-        MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)]
-            = MI.VTableIndex - AddressPoint;
+        MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)] =
+            MI.VTableIndex - AddressPoint;
         MethodVTableIndices[GlobalDecl(DD, Dtor_Deleting)]
             = MI.VTableIndex + 1 - AddressPoint;
       } else {
@@ -2655,7 +2655,10 @@ class VFTableBuilder {
       MethodVFTableLocation Loc(MI.VBTableIndex, WhichVFPtr.getVBaseWithVPtr(),
                                 WhichVFPtr.NonVirtualOffset, MI.VFTableIndex);
       if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
-        MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
+        if (!Context.getTargetInfo().getCXXABI().isMicrosoft())
+          MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
+        else
+          MethodVFTableLocations[GlobalDecl(DD, Dtor_VectorDeleting)] = Loc;
       } else {
         MethodVFTableLocations[MD] = Loc;
       }
@@ -3285,7 +3288,10 @@ void VFTableBuilder::dumpLayout(raw_ostream &Out) {
       const CXXDestructorDecl *DD = Component.getDestructorDecl();
 
       DD->printQualifiedName(Out);
-      Out << "() [scalar deleting]";
+      if (Context.getTargetInfo().getCXXABI().isMicrosoft())
+        Out << "() [vector deleting]";
+      else
+        Out << "() [scalar deleting]";
 
       if (DD->isPureVirtual())
         Out << " [pure]";
@@ -3756,7 +3762,7 @@ void MicrosoftVTableContext::dumpMethodLocations(
         PredefinedIdentKind::PrettyFunctionNoVirtual, MD);
 
     if (isa<CXXDestructorDecl>(MD)) {
-      IndicesMap[I.second] = MethodName + " [scalar deleting]";
+      IndicesMap[I.second] = MethodName + " [vector deleting]";
     } else {
       IndicesMap[I.second] = MethodName;
     }
@@ -3873,7 +3879,7 @@ MicrosoftVTableContext::getMethodVFTableLocation(GlobalDecl GD) {
   assert(hasVtableSlot(cast<CXXMethodDecl>(GD.getDecl())) &&
          "Only use this method for virtual methods or dtors");
   if (isa<CXXDestructorDecl>(GD.getDecl()))
-    assert(GD.getDtorType() == Dtor_Deleting);
+    assert(GD.getDtorType() == Dtor_VectorDeleting);
 
   GD = GD.getCanonicalDecl();
 
diff --git a/clang/lib/CodeGen/CGCXX.cpp b/clang/lib/CodeGen/CGCXX.cpp
index 78a7b021855b7e8..6f47e24eed5b374 100644
--- a/clang/lib/CodeGen/CGCXX.cpp
+++ b/clang/lib/CodeGen/CGCXX.cpp
@@ -175,7 +175,6 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
   // requires explicit comdat support in the IL.
   if (llvm::GlobalValue::isWeakForLinker(TargetLinkage))
     return true;
-
   // Create the alias with no name.
   auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
                                           Aliasee, &getModule());
@@ -201,6 +200,42 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
   return false;
 }
 
+/// Emit a definition as a global alias for another definition, unconditionally.
+void CodeGenModule::EmitDefinitionAsAlias(GlobalDecl AliasDecl,
+                                          GlobalDecl TargetDecl) {
+
+  llvm::Type *AliasValueType = getTypes().GetFunctionType(AliasDecl);
+
+  StringRef MangledName = getMangledName(AliasDecl);
+  llvm::GlobalValue *Entry = GetGlobalValue(MangledName);
+  if (Entry && !Entry->isDeclaration())
+    return;
+  auto *Aliasee = cast<llvm::GlobalValue>(GetAddrOfGlobal(TargetDecl));
+
+  // Determine the linkage type for the alias.
+  llvm::GlobalValue::LinkageTypes Linkage = getFunctionLinkage(AliasDecl);
+
+  // Create the alias with no name.
+  auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
+                                          Aliasee, &getModule());
+  // Destructors are always unnamed_addr.
+  Alias->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+
+  if (Entry) {
+    assert(Entry->getValueType() == AliasValueType &&
+           Entry->getAddressSpace() == Alias->getAddressSpace() &&
+           "declaration exists with different type");
+    Alias->takeName(Entry);
+    Entry->replaceAllUsesWith(Alias);
+    Entry->eraseFromParent();
+  } else {
+    Alias->setName(MangledName);
+  }
+
+  // Set any additional necessary attributes for the alias.
+  SetCommonAttributes(AliasDecl, Alias);
+}
+
 llvm::Function *CodeGenModule::codegenCXXStructor(GlobalDecl GD) {
   const CGFunctionInfo &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD);
   auto *Fn = cast<llvm::Function>(
diff --git a/clang/lib/CodeGen/CGCXXABI.cpp b/clang/lib/CodeGen/CGCXXABI.cpp
index 7c6dfc3e59d8c0e..e109fd0c443f6a0 100644
--- a/clang/lib/CodeGen/CGCXXABI.cpp
+++ b/clang/lib/CodeGen/CGCXXABI.cpp
@@ -273,6 +273,20 @@ void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
   numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
 }
 
+void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
+                               QualType eltTy, llvm::Value *&numElements,
+                               llvm::Value *&allocPtr, CharUnits &cookieSize) {
+  assert(eltTy.isDestructedType());
+
+  // Derive a char* in the same address space as the pointer.
+  ptr = ptr.withElementType(CGF.Int8Ty);
+
+  cookieSize = getArrayCookieSizeImpl(eltTy);
+  Address allocAddr = CGF.Builder.CreateConstInBoundsByteGEP(ptr, -cookieSize);
+  allocPtr = allocAddr.emitRawPointer(CGF);
+  numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
+}
+
 llvm::Value *CGCXXABI::readArrayCookieImpl(CodeGenFunction &CGF,
                                            Address ptr,
                                            CharUnits cookieSize) {
diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h
index 687ff7fb844445a..fbbe8f6c6142cb7 100644
--- a/clang/lib/CodeGen/CGCXXABI.h
+++ b/clang/lib/CodeGen/CGCXXABI.h
@@ -251,9 +251,10 @@ class CGCXXABI {
 
 public:
   virtual void emitVirtualObjectDelete(CodeGenFunction &CGF,
-                                       const CXXDeleteExpr *DE,
-                                       Address Ptr, QualType ElementType,
-                                       const CXXDestructorDecl *Dtor) = 0;
+                                       const CXXDeleteExpr *DE, Address Ptr,
+                                       QualType ElementType,
+                                       const CXXDestructorDecl *Dtor,
+                                       bool ArrayDeletion) = 0;
   virtual void emitRethrow(CodeGenFunction &CGF, bool isNoReturn) = 0;
   virtual void emitThrow(CodeGenFunction &CGF, const CXXThrowExpr *E) = 0;
   virtual llvm::GlobalVariable *getThrowInfo(QualType T) { return nullptr; }
@@ -275,6 +276,7 @@ class CGCXXABI {
   virtual CatchTypeInfo getCatchAllTypeInfo();
 
   virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
+  virtual bool hasVectorDeletingDtors() = 0;
   virtual void EmitBadTypeidCall(CodeGenFunction &CGF) = 0;
   virtual llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
                                   Address ThisPtr,
@@ -485,11 +487,10 @@ class CGCXXABI {
       llvm::PointerUnion<const CXXDeleteExpr *, const CXXMemberCallExpr *>;
 
   /// Emit the ABI-specific virtual destructor call.
-  virtual llvm::Value *
-  EmitVirtualDestructorCall(CodeGenFunction &CGF, const CXXDestructorDecl *Dtor,
-                            CXXDtorType DtorType, Address This,
-                            DeleteOrMemberCallExpr E,
-                            llvm::CallBase **CallOrInvoke) = 0;
+  virtual llvm::Value *EmitVirtualDestructorCall(
+      CodeGenFunction &CGF, const CXXDestructorDecl *Dtor, CXXDtorType DtorType,
+      Address This, DeleteOrMemberCallExpr E, llvm::CallBase **CallOrInvoke,
+      bool ArrayDeletion = false) = 0;
 
   virtual void adjustCallArgsForDestructorThunk(CodeGenFunction &CGF,
                                                 GlobalDecl GD,
@@ -575,6 +576,12 @@ class CGCXXABI {
                                QualType ElementType, llvm::Value *&NumElements,
                                llvm::Value *&AllocPtr, CharUnits &CookieSize);
 
+  /// Reads the array cookie associated with the given pointer,
+  /// that should have one.
+  virtual void ReadArrayCookie(CodeGenFunction &CGF, Address Ptr,
+                               QualType ElementType, llvm::Value *&NumElements,
+                               llvm::Value *&AllocPtr, CharUnits &CookieSize);
+
   /// Return whether the given global decl needs a VTT parameter.
   virtual bool NeedsVTTParameter(GlobalDecl GD);
 
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index 7a1096fcbca822b..bb94d502c34faf1 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -1433,6 +1433,83 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF,
   return true;
 }
 
+namespace {
+llvm::Value *LoadThisForDtorDelete(CodeGenFunction &CGF,
+                                   const CXXDestructorDecl *DD) {
+  if (Expr *ThisArg = DD->getOperatorDeleteThisArg())
+    return CGF.EmitScalarExpr(ThisArg);
+  return CGF.LoadCXXThis();
+}
+}
+
+void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD,
+                                  CodeGenFunction &CGF,
+                                  llvm::Value *ShouldDeleteCondition) {
+  Address ThisPtr = CGF.LoadCXXThisAddress();
+  llvm::BasicBlock *ScalarBB = CGF.createBasicBlock("dtor.scalar");
+  llvm::BasicBlock *callDeleteBB =
+      CGF.createBasicBlock("dtor.call_delete_after_array_destroy");
+  llvm::BasicBlock *VectorBB = CGF.createBasicBlock("dtor.vector");
+  auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+  llvm::Value *CheckTheBitForArrayDestroy = CGF.Builder.CreateAnd(
+      ShouldDeleteCondition,
+      llvm::Constant::getIntegerValue(CondTy, llvm::APInt(CondTy->getBitWidth(),
+                                                          /*val=*/2)));
+  llvm::Value *ShouldDestroyArray =
+      CGF.Builder.CreateIsNull(CheckTheBitForArrayDestroy);
+  CGF.Builder.CreateCondBr(ShouldDestroyArray, ScalarBB, VectorBB);
+
+  CGF.EmitBlock(VectorBB);
+
+  llvm::Value *numElements = nullptr;
+  llvm::Value *allocatedPtr = nullptr;
+  CharUnits cookieSize;
+  QualType EltTy = DD->getThisType()->getPointeeType();
+  CGF.CGM.getCXXABI().ReadArrayCookie(CGF, ThisPtr, EltTy, numElements,
+                                      allocatedPtr, cookieSize);
+
+  // Destroy the elements.
+  QualType::DestructionKind dtorKind = EltTy.isDestructedType();
+
+  assert(dtorKind);
+  assert(numElements && "no element count for a type with a destructor!");
+
+  CharUnits elementSize = CGF.getContext().getTypeSizeInChars(EltTy);
+  CharUnits elementAlign =
+      ThisPtr.getAlignment().alignmentOfArrayElement(elementSize);
+
+  llvm::Value *arrayBegin = ThisPtr.emitRawPointer(CGF);
+  llvm::Value *arrayEnd = CGF.Builder.CreateInBoundsGEP(
+      ThisPtr.getElementType(), arrayBegin, numElements, "delete.end");
+
+  // We already checked that the array is not 0-length before entering vector
+  // deleting dtor.
+  CGF.emitArrayDestroy(arrayBegin, arrayEnd, EltTy, elementAlign,
+                       CGF.getDestroyer(dtorKind),
+                       /*checkZeroLength*/ false, CGF.needsEHCleanup(dtorKind));
+
+  llvm::BasicBlock *VectorBBCont = CGF.createBasicBlock("dtor.vector.cont");
+  CGF.EmitBlock(VectorBBCont);
+
+  llvm::Value *CheckTheBitForDeleteCall = CGF.Builder.CreateAnd(
+      ShouldDeleteCondition,
+      llvm::Constant::getIntegerValue(CondTy, llvm::APInt(CondTy->getBitWidth(),
+                                                          /*val=*/1)));
+
+  llvm::Value *ShouldCallDelete =
+      CGF.Builder.CreateIsNull(CheckTheBitForDeleteCall);
+  CGF.Builder.CreateCondBr(ShouldCallDelete, CGF.ReturnBlock.getBlock(),
+                           callDeleteBB);
+  CGF.EmitBlock(callDeleteBB);
+  const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
+  const CXXRecordDecl *ClassDecl = Dtor->getParent();
+  CGF.EmitDeleteCall(Dtor->getOperatorDelete(), allocatedPtr,
+                     CGF.getContext().getTagDeclType(ClassDecl));
+
+  CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
+  CGF.EmitBlock(ScalarBB);
+}
+
 /// EmitDestructorBody - Emits the body of the current destructor.
 void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CurGD.getDecl());
@@ -1462,7 +1539,9 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   // outside of the function-try-block, which means it's always
   // possible to delegate the destructor body to the complete
   // destructor.  Do so.
-  if (DtorType == Dtor_Deleting) {
+  if (DtorType == Dtor_Deleting || DtorType == Dtor_VectorDeleting) {
+    if (CXXStructorImplicitParamValue && DtorType == Dtor_VectorDeleting)
+      EmitConditionalArrayDtorCall(Dtor, *this, CXXStructorImplicitParamValue);
     RunCleanupsScope DtorEpilogue(*this);
     EnterDtorCleanups(Dtor, Dtor_Deleting);
     if (HaveInsertPoint()) {
@@ -1491,6 +1570,7 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
   switch (DtorType) {
   case Dtor_Comdat: llvm_unreachable("not expecting a COMDAT");
   case Dtor_Deleting: llvm_unreachable("already handled deleting case");
+  case Dtor_VectorDeleting: llvm_unreachable("already handled vector deleting case");
 
   case Dtor_Complete:
     assert((Body || getTarget().getCXXABI().isMicrosoft()) &&
@@ -1567,13 +1647,6 @@ void CodeGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &Args)
 }
 
 namespace {
-  llvm::Value *LoadThisForDtorDelete(CodeGenFunction &CGF,
-                                     const CXXDestructorDecl *DD) {
-    if (Expr *ThisArg = DD->getOperatorDeleteThisArg())
-      return CGF.EmitScalarExpr(ThisArg);
-    return CGF.LoadCXXThis();
-  }
-
   /// Call the operator delete associated with the current destructor.
   struct CallDtorDelete final : EHScopeStack::Cleanup {
     CallDtorDelete() {}
@@ -1592,8 +1665,12 @@ namespace {
                                      bool ReturnAfterDelete) {
     llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete");
     llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue");
-    llvm::Value *ShouldCallDelete
-      = CGF.Builder.CreateIsNull(ShouldDeleteCondition);
+    auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
+    llvm::Value *CheckTheBit = CGF.Builder.CreateAnd(
+        ShouldDeleteCondition, llvm::Constant::getIntegerValue(
+                                   CondTy, llvm::APInt(CondTy->getBitWidth(),
+                                                       /*val=*/1)));
+    llvm::Value *ShouldCallDelete = CGF.Builder.CreateIsNull(CheckTheBit);
     CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB);
 
     CGF.EmitBlock(callDeleteBB);
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index d5b584ec0f2e95b..da514ff612a32ef 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -2104,7 +2104,8 @@ llvm::DISubprogram *CGDebugInfo::CreateCXXMemberFunction(
       // Emit MS ABI vftable inf...
[truncated]

Copy link

github-actions bot commented Feb 7, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@Fznamznon
Copy link
Contributor Author

Ping.

Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch, hopefully these comments are actionable

@@ -2013,7 +2023,7 @@ llvm::Value *MicrosoftCXXABI::EmitVirtualDestructorCall(
ASTContext &Context = getContext();
llvm::Value *ImplicitParam = llvm::ConstantInt::get(
llvm::IntegerType::getInt32Ty(CGF.getLLVMContext()),
DtorType == Dtor_Deleting);
2 * (ArrayDeletion) + (DtorType == Dtor_Deleting));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bitfield parameter, so I think for readability it would be better to express this with shifts and ors, so something like:

  uint32_t Flags = (ArrayDeletion << 1) | (DtorType == Dtor_Deleting);
  llvm::Value *ImplicitParam = CGF.Builder.getInt32(Flags);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, makes sense, thanks.

!CGM.classNeedsVectorDestructor(dtor->getParent())) {
// Create GlobalDecl objects with the correct type for the vector and scalar
// deleting destructors.
GlobalDecl VectorDtorGD(dtor, Dtor_VectorDeleting);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just use GD here rather than creating a new one, since they should be equivalent and GlobalDecl is a multi-word object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@rnk
Copy link
Collaborator

rnk commented Feb 20, 2025

Thanks for the updates, I think this is almost done.

@zmodem , this will probably have some impact on Chrome code size and will probably churn many crash stack traces. I think it's safe to rely on all the existing automated testing, but this is a high-risk change to keep in mind if any Windows-specific issues arise.

@Fznamznon Fznamznon requested a review from rnk February 21, 2025 10:04
@zmodem
Copy link
Collaborator

zmodem commented Feb 21, 2025

this is a high-risk change to keep in mind if any Windows-specific issues arise.

Thanks for the heads up!

Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, these comments didn't post. They may be stale. I will post them and revisit them.

@@ -2657,7 +2657,10 @@ class VFTableBuilder {
MethodVFTableLocation Loc(MI.VBTableIndex, WhichVFPtr.getVBaseWithVPtr(),
WhichVFPtr.NonVirtualOffset, MI.VFTableIndex);
if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
if (!Context.getTargetInfo().getCXXABI().isMicrosoft())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code golf nit: Rather than duplicating the densemap insertion you can select the appropriate dtor type and construct the GlobalDecl with a dtor type variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done.

@@ -575,6 +576,12 @@ class CGCXXABI {
QualType ElementType, llvm::Value *&NumElements,
llvm::Value *&AllocPtr, CharUnits &CookieSize);

/// Reads the array cookie associated with the given pointer,
/// that should have one.
virtual void ReadArrayCookie(CodeGenFunction &CGF, Address Ptr,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are no overrides, this doesn't need to be virtual.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, removed virtual. Does it make sense to do that for another overload of ReadArrayCookie as well?

Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I resolved two conversations, but there are two actionable comments.

@zmodem
Copy link
Collaborator

zmodem commented Mar 13, 2025

It turns out another issue (https://crbug.com/402425841) also bisected to this PR. That one is a run-time problem, so it may be trickier to figure out, but I will look into it next.

The bugs seem related by both involving the ICU library, and if squinting a bit it seems they might both involve UnicodeString:

It's not really clear which relocations against which .text section the linker is complaining about, but .SCOVP$M has a relocation against ??_EUnicodeString@icu_74@@UEAAPEAXI@Z (public: virtual void * __ptr64 __cdecl icu_74::UnicodeString::vector deleting destructor'(unsigned int) __ptr64) as well as five against some .text section here:

$ build/bin/llvm-objdump -r repro.bad/C/src/chromium/src/out/Release/obj/third_party/icu/icuuc_private/filteredbrk.obj
[...]
RELOCATION RECORDS FOR [.SCOVP$M]:
OFFSET           TYPE                     VALUE
0000000000000000 IMAGE_REL_AMD64_ADDR64   ??_EUnicodeString@icu_74@@UEAAPEAXI@Z
0000000000000010 IMAGE_REL_AMD64_ADDR64   .text
0000000000000020 IMAGE_REL_AMD64_ADDR64   .text
0000000000000030 IMAGE_REL_AMD64_ADDR64   .text
0000000000000040 IMAGE_REL_AMD64_ADDR64   .text
0000000000000050 IMAGE_REL_AMD64_ADDR64   .text

and that's the only vector deleting destructor I find referenced from .SCOVP$M.

In the crash from https://crbug.com/402425841 the code seems to segfault while doing something with UnicodeString's vftable:

(12f60.1df84): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for gen-regexp-special-case.exe.exe
gen_regexp_special_case_exe!icu_74::UnicodeString::~UnicodeString+0x6:
0076c216 c70154a37f00    mov     dword ptr [ecx],offset gen_regexp_special_case_exe!icu_74::UnicodeString::`vftable' (007fa354) ds:002b:00000001=????????

(12f60.1df84): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for gen-regexp-special-case.exe.exe
gen_regexp_special_case_exe!icu_74::UnicodeString::~UnicodeString+0x6:
0076c216 c70154a37f00    mov     dword ptr [ecx],offset gen_regexp_special_case_exe!icu_74::UnicodeString::`vftable' (007fa354) ds:002b:00000001=????????
0:000:x86> k
 # ChildEBP RetAddr      
00 04cff514 007c0478     gen_regexp_special_case_exe!icu_74::UnicodeString::~UnicodeString+0x6 [o:\third_party\icu\source\common\unistr.cpp @ 423] 
01 04cff528 0076988e     gen_regexp_special_case_exe!icu_74::UnicodeString::~UnicodeString+0x18 [o:\third_party\icu\source\common\unicode\unistr.h @ 3346] 
02 04cff53c 00774f0b     gen_regexp_special_case_exe!uprv_deleteUObject_74+0x1e [o:\third_party\icu\source\common\uobject.cpp @ 105] 
03 (Inline) --------     gen_regexp_special_case_exe!icu_74::UVector::removeAllElements+0x2d [o:\third_party\icu\source\common\uvector.cpp @ 255] 
04 (Inline) --------     gen_regexp_special_case_exe!icu_74::UVector::~UVector+0x33 [o:\third_party\icu\source\common\uvector.cpp @ 64] 
05 04cff554 00777f24     gen_regexp_special_case_exe!icu_74::UVector::~UVector+0x3b [o:\third_party\icu\source\common\uvector.cpp @ 63] 
06 04cff570 0077da1b     gen_regexp_special_case_exe!icu_74::UnicodeSet::~UnicodeSet+0x64 [o:\third_party\icu\source\common\uniset.cpp @ 199] 
07 04cff728 0077d504     gen_regexp_special_case_exe!icu_74::UnicodeSet::closeOverCaseInsensitive+0x4fb [o:\third_party\icu\source\common\uniset_closure.cpp @ 295] 
08 04cff738 0074137d     gen_regexp_special_case_exe!icu_74::UnicodeSet::closeOver+0x34 [o:\third_party\icu\source\common\uniset_closure.cpp @ 228] 
09 04cffa08 00741ad9     gen_regexp_special_case_exe!v8::internal::PrintSpecial+0xbd [o:\v8\src\regexp\gen-regexp-special-case.cc @ 70] 
0a 04cffae4 00741bc5     gen_regexp_special_case_exe!v8::internal::WriteHeader+0x229 [o:\v8\src\regexp\gen-regexp-special-case.cc @ 148] 
0b 04cffaf4 007ccb5c     gen_regexp_special_case_exe!main+0x15 [o:\v8\src\regexp\gen-regexp-special-case.cc @ 164] 
0c (Inline) --------     gen_regexp_special_case_exe!invoke_main+0x1c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78] 
0d 04cffb3c 76b77ba9     gen_regexp_special_case_exe!__scrt_common_main_seh+0xfa [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
0e 04cffb4c 77acc28b     KERNEL32!BaseThreadInitThunk+0x19
0f 04cffba4 77acc20f     ntdll32!__RtlUserThreadStart+0x2b
10 04cffbb4 00000000     ntdll32!_RtlUserThreadStart+0x1b

So maybe there is something wrong with the vector deleting destructor for ICU's UnicodeString or how it's being used.

@Fznamznon
Copy link
Contributor Author

Reduced linkage failure reproducer (can be reduced further, I'm sure), it consists of 4 files

t.h:


#include <new>

struct Base {

   Base() {}

   virtual ~Base() {}

};

struct ClassA : public Base {
  inline ClassA();
  virtual ~ClassA(){} ;
};

inline
ClassA::ClassA() {}

void foo();

t.cpp

#include "t.h"

int main()

{   ClassA* objs = new ClassA[5];   delete[] objs; 
                                                   foo();  return 0; }

t1.cpp

#include "t.h"

void foo()

{   ClassA* objs = new ClassA[5];   delete[] objs; }

t3.cpp

#include "t.h"

void foobar()

{   ClassA* objs = new ClassA;   delete objs; }

Then

clang-cl -c t.cpp /Zi -fsanitize=address,fuzzer-no-link
clang-cl -c t1.cpp /Zi -fsanitize=address,fuzzer-no-link
clang-cl -c t3.cpp /Zi -fsanitize=address,fuzzer-no-link
lld-link.exe t.obj t1.obj t1.obj t3.obj "-libpath:path_to_llvm\lib\clang\21\lib\windows" "path_to_llvm\lib\clang\21\lib\windows\clang_rt.asan_dynamic-x86_64.lib" "-wholearchive:path_to_llvm\lib\clang\21\lib\windows\clang_rt.asan_static_runtime_thunk-x86_64.lib"\


lld-link: error: relocation against symbol in discarded section: .text
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)

lld-link: error: relocation against symbol in discarded section: .text
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)

lld-link: error: relocation against symbol in discarded section: .text
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)

lld-link: error: relocation against symbol in discarded section: .text
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)

lld-link: error: relocation against symbol in discarded section: .text
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)
>>> referenced by t1.obj:(.SCOVP$M)

t.obj and t1.cpp will trigger vector deleting destructor, t3.cpp will trigger scalar deleting destructor and a weak alias. What I've noticed is that LINK.exe (Microsoft linker) is fine with these 3 object files. I don't know what is going on during linkage when one object file has a definition of vector deleting destructor, but another object file has only an alias. vector deleting destructors are generated by this patch with weak linkage. Perhaps support is needed in lld-link for that case?

The reason why it is triggered by sanitizer build, adding -fsanitize=fuzzer-no-link option causes adding a bunch of global variables to IR that reference the destructors and marked with SCOVP$M section:

@__sancov_gen_.3 = private constant [10 x ptr] [ptr @"??_EClassA@@UEAAPEAXI@Z", ptr inttoptr (i64 1 to ptr), ptr blockaddress(@"??_EClassA@@UEAAPEAXI@Z", %dtor.vector.dtor.continue_crit_edge), ptr null, ptr blockaddress(@"??_EClassA@@UEAAPEAXI@Z", %dtor.call_delete_after_array_destroy), ptr null, ptr blockaddress(@"??_EClassA@@UEAAPEAXI@Z", %dtor.scalar.dtor.continue.sink.split_crit_edge), ptr null, ptr blockaddress(@"??_EClassA@@UEAAPEAXI@Z", %dtor.scalar.dtor.continue_crit_edge), ptr null], section ".SCOVP$M", align 8

I'm now heading to the weekend, but I'll look a bit more before claiming problems in lld-link

@zmodem , thanks for the findings

So maybe there is something wrong with the vector deleting destructor for ICU's UnicodeString or how it's being used.

but, that is still not a great reproducer for a reverted patch. Especially if the link issue is due to a missing support in linker. I would appreciate if you could maybe point to how exactly UnicodeString is used in failing scenario.

@rnk
Copy link
Collaborator

rnk commented Mar 14, 2025

I think the .SCOV$M section globals should be in the ??_EClassA@@UEAAPEXI@Z comdat group. I think sanitizer coverage (sancov) is one of the lesser-used sanitizers here, and it may lack some sophistication when it comes to comdat groups. I wasn't aware of this creative use of blockaddress until just now.

@zmodem
Copy link
Collaborator

zmodem commented Mar 17, 2025

but, that is still not a great reproducer for a reverted patch. Especially if the link issue is due to a missing support in linker. I would appreciate if you could maybe point to how exactly UnicodeString is used in failing scenario.

I'm sorry that we didn't catch this problem sooner and were able to provide better reproducers.

I still think reverting was right though. There is clearly a subtle problem here -- both at link time and run time -- and the fewer people who run into that the better. Even if it turns out that the root cause lies in lld.

I've uploaded a stand-alone from-source reproducer of the run time crash here: https://crbug.com/402425841#comment10

frederik-h pushed a commit to frederik-h/llvm-project that referenced this pull request Mar 18, 2025
…#126240)"

This caused link errors when building with sancov. See comment on the PR.

> Whereas it is UB in terms of the standard to delete an array of objects
> via pointer whose static type doesn't match its dynamic type, MSVC
> supports an extension allowing to do it.
> Aside from array deletion not working correctly in the mentioned case,
> currently not having this extension implemented causes clang to generate
> code that is not compatible with the code generated by MSVC, because
> clang always puts scalar deleting destructor to the vftable. This PR
> aims to resolve these problems.
>
> Fixes llvm#19772

This reverts commit d6942d5.
@Fznamznon
Copy link
Contributor Author

I think the .SCOV$M section globals should be in the ??_EClassA@@UEAAPEXI@Z comdat group. I think sanitizer coverage (sancov) is one of the lesser-used sanitizers here, and it may lack some sophistication when it comes to comdat groups. I wasn't aware of this creative use of blockaddress until just now.

I see that similar globals generated for scalar deleting destructors are actually in the corresponding comdat groups. I suppose it might be due to this check

(TargetTriple.isOSBinFormatELF() || !F.isInterposable()))

that F.isInterposable() . It then calls isInterposableLinkage() that returns false for weak non-odr linkage which is used for vector deleting dtor. I am now wondering if we should not emit vector deleting destructors with weak linkage.

@rnk
Copy link
Collaborator

rnk commented Mar 18, 2025

If we can separate the linkage of the alias and the vector deleting destructor definition, that might work. We need to block globalopt and other passes from looking through the alias and inlining scalar deleting destructor calls unless we can prove that the object being destroyed was constructed with non-array new. This is just not modeled, so the safest thing is to leave the alias with weak linkage.

I threw together PR #131929 to tweak the condition there in sancov if you want to stamp it. I think it's an improvement on its own. Sancov should put these metadata sections into comdats if the existing function already has a comdat, regardless of what the linkage is.

@Fznamznon
Copy link
Contributor Author

I've uploaded a stand-alone from-source reproducer of the run time crash here: https://crbug.com/402425841#comment10

Reproduced. Seems limited to 32-bit build (only reproducible with -m32 option) and lld linker. Fine with either standard linker or 64-bit build.

rnk added a commit that referenced this pull request Mar 19, 2025
This code avoids adding comdat groups to interposable linkage types
(weak, linkonce (non-ODR)) to avoid changing semantics, since comdat
elimination happens before weak/strong prevailaing symbol resolution.
However, if the function is already in a comdat, we can add to the group
without changing the semantics of the linked program.

Fixes an issue uncovered in PR #126240
jph-13 pushed a commit to jph-13/llvm-project that referenced this pull request Mar 21, 2025
Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

Fixes llvm#19772
@Fznamznon
Copy link
Contributor Author

So, the body of the destructor is not the problem. I commented out all new additions to body emission of the destructor and it still crashes.
What makes the difference is the alias emission. I commented out alias emission and it started passing with "_E" destructor always emitted.

@Fznamznon
Copy link
Contributor Author

The problem also is only reproducible when using -start-lib -end-lib options around icu library object files in the linking command.

@Fznamznon
Copy link
Contributor Author

I also noticed that removing -DU_STATIC_IMPLEMENTATION macro passing fixes the crash.

@Fznamznon
Copy link
Contributor Author

Fznamznon commented Mar 26, 2025

I reduced main code to

#include "unicode/filteredbrk.h"
#include <stdio.h>

using namespace icu;

int main(int argc, const char** argv) {
  UErrorCode status = U_ZERO_ERROR;
  //BreakIterator* bi = BreakIterator::createWordInstance(root, status); // Any iterator will do, make sure filteredbrk.cpp is involved. 
// This file explicitly does new[] on UnicodeString, but so does unistr.cpp as well.
// The comments say it is to force emission of vector deleting dtor and commenting it out doesn't do anything.
  FilteredBreakIteratorBuilder* fbiBuilder = FilteredBreakIteratorBuilder::createInstance(status);

  icu::UnicodeString* St = new icu::UnicodeString("abacabadabacab", 15);

  delete St;

  printf("OKAY\n");
  return 0;
}

In order to avoid UniSet creation.
That still does not give a clear understanding of what is going on because the reproducer is massive and entangled. For some reason pointer to vtable is corrupted when the destructor of UnicodeString is called.

@zmodem any chance you could help to reduce it further? I already spent reasonable amount of time doing this without a good output.

This does seem like a problem that may have been introduced by the lld. The fact that it all passes in x64 mode, or without alias emission, or without -start-lib -end-lib options. But I am still not sure. I would appreciate any thoughts on this. @rnk , any chance you might be aware of some linkage-related specific affecting vector deleting destructors when building 32-bit applications?

@rnk
Copy link
Collaborator

rnk commented Mar 26, 2025

I'll try to take a look at this when I get a chance, but that looks like it's not happening today.

I read the comments a bit and found some codesearch links worth sharing for reference. Here's the original UnicodeString array new operation:
https://source.chromium.org/chromium/chromium/src/+/main:third_party/icu/source/common/filteredbrk.cpp;l=583;drc=e8c3bc9ea97d4423ad0515e5f1c064f486dae8b1

UnicodeString is a dynamic, polymorphic object with a virtual destructor, as one would expect. There are no DLLs in the build script.

I think removing the U_STATIC_IMPLEMENTATION define makes all these classes dllexport, which disables our vector/scalar alias optimization, hiding the problem.

More debugging is required.

@zmodem
Copy link
Collaborator

zmodem commented Mar 27, 2025

A few notes from looking today.

Reduced main a bit further:

int main(int argc, const char** argv) {
  volatile auto p = &FilteredBreakIteratorBuilder::createEmptyInstance;
  icu::UnicodeString* St = new icu::UnicodeString("abacabadabacab", 15);
  delete St;
  printf("OKAY\n");
  return 0;
}

That is, we don't need to run any code from filteredbrk.cpp, just reference it.

Since the code won't be run, we can strip as much of it as possible. (Attaching what I got so far:
filteredbrk.cpp.txt)

These pieces of code are needed to keep reproducing:

SimpleFilteredBreakIteratorBuilder::SimpleFilteredBreakIteratorBuilder(const Locale &fromLocale, UErrorCode &status)
  : fSet(status) {
   UnicodeString s;
}

static inline UnicodeString* newUnicodeStringArray(size_t count) {
    return new UnicodeString[count ? count : 1];
}
BreakIterator *
SimpleFilteredBreakIteratorBuilder::build(BreakIterator* adoptBreakIterator, UErrorCode& status) {
  int32_t subCount = fSet.size();
  UnicodeString *ustrs_ptr = newUnicodeStringArray(subCount);
}

So we've got both scalar new and regular construction there.

What's really interesting is that if I manually inline newUnicodeStringArray into build like this:

BreakIterator *
SimpleFilteredBreakIteratorBuilder::build(BreakIterator* adoptBreakIterator, UErrorCode& status) {
  int32_t subCount = fSet.size();
  UnicodeString *ustrs_ptr = new UnicodeString[subCount ? subCount : 1];
}

the crash goes away. That seems like a major hint. What's different in the object file before/after that manual inlining, and how might that affect linking?

@Fznamznon
Copy link
Contributor Author

Thank you for your help @rnk @zmodem !

@Fznamznon
Copy link
Contributor Author

Fznamznon commented Mar 27, 2025

Removing static from newUnicodeStringArray also makes the crash go away... I also noticed that in the failing scenario in LLVM IR vector deleting destructor is missing unnamed_addr for some reason. Added it, but it doesn't solve the crash.

@Fznamznon
Copy link
Contributor Author

Soo, I restored all the attributes on vector deleting destructor and the calling convention (so they are the same as in non-failing case) and the crash is gone. I'll put a PR reapplying the patch soon. The reduction helped a lot, thanks!

Fznamznon added a commit to Fznamznon/llvm-project that referenced this pull request Mar 28, 2025
Fznamznon added a commit that referenced this pull request Mar 31, 2025
Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer
coverage enabled,
which is fixed by #131929 .

The second commit of this PR also contains a fix for a runtime failure
in chromium reported
in
#126240 (comment)
.

Fixes #19772
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Mar 31, 2025
…tors (#133451)

Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer
coverage enabled,
which is fixed by llvm/llvm-project#131929 .

The second commit of this PR also contains a fix for a runtime failure
in chromium reported
in
llvm/llvm-project#126240 (comment)
.

Fixes llvm/llvm-project#19772
SchrodingerZhu pushed a commit to SchrodingerZhu/llvm-project that referenced this pull request Mar 31, 2025
…133451)

Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer
coverage enabled,
which is fixed by llvm#131929 .

The second commit of this PR also contains a fix for a runtime failure
in chromium reported
in
llvm#126240 (comment)
.

Fixes llvm#19772
KornevNikita pushed a commit to intel/llvm that referenced this pull request May 27, 2025
Whereas it is UB in terms of the standard to delete an array of objects
via pointer whose static type doesn't match its dynamic type, MSVC
supports an extension allowing to do it.
Aside from array deletion not working correctly in the mentioned case,
currently not having this extension implemented causes clang to generate
code that is not compatible with the code generated by MSVC, because
clang always puts scalar deleting destructor to the vftable. This PR
aims to resolve these problems.

It was reverted due to link time errors in chromium with sanitizer
coverage enabled,
which is fixed by llvm/llvm-project#131929 .

The second commit of this PR also contains a fix for a runtime failure
in chromium reported
in
llvm/llvm-project#126240 (comment)
.

Fixes llvm/llvm-project#19772
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category debuginfo
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement vector deleting destructors
5 participants