Skip to content

Commit d6942d5

Browse files
authored
[MS][clang] Add support for vector deleting destructors (llvm#126240)
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
1 parent a619a2e commit d6942d5

34 files changed

+510
-125
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ Android Support
308308
Windows Support
309309
^^^^^^^^^^^^^^^
310310

311+
- Clang now supports MSVC vector deleting destructors (GH19772).
312+
311313
LoongArch Support
312314
^^^^^^^^^^^^^^^^^
313315

clang/include/clang/AST/VTableBuilder.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class VTableComponent {
150150

151151
bool isRTTIKind() const { return isRTTIKind(getKind()); }
152152

153-
GlobalDecl getGlobalDecl() const {
153+
GlobalDecl getGlobalDecl(bool HasVectorDeletingDtors) const {
154154
assert(isUsedFunctionPointerKind() &&
155155
"GlobalDecl can be created only from virtual function");
156156

@@ -161,7 +161,9 @@ class VTableComponent {
161161
case CK_CompleteDtorPointer:
162162
return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Complete);
163163
case CK_DeletingDtorPointer:
164-
return GlobalDecl(DtorDecl, CXXDtorType::Dtor_Deleting);
164+
return GlobalDecl(DtorDecl, (HasVectorDeletingDtors)
165+
? CXXDtorType::Dtor_VectorDeleting
166+
: CXXDtorType::Dtor_Deleting);
165167
case CK_VCallOffset:
166168
case CK_VBaseOffset:
167169
case CK_OffsetToTop:

clang/include/clang/Basic/ABI.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ enum CXXCtorType {
3131

3232
/// C++ destructor types.
3333
enum CXXDtorType {
34-
Dtor_Deleting, ///< Deleting dtor
35-
Dtor_Complete, ///< Complete object dtor
36-
Dtor_Base, ///< Base object dtor
37-
Dtor_Comdat ///< The COMDAT used for dtors
34+
Dtor_Deleting, ///< Deleting dtor
35+
Dtor_Complete, ///< Complete object dtor
36+
Dtor_Base, ///< Base object dtor
37+
Dtor_Comdat, ///< The COMDAT used for dtors
38+
Dtor_VectorDeleting ///< Vector deleting dtor
3839
};
3940

4041
} // end namespace clang

clang/lib/AST/ItaniumMangle.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6014,6 +6014,8 @@ void CXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
60146014
case Dtor_Comdat:
60156015
Out << "D5";
60166016
break;
6017+
case Dtor_VectorDeleting:
6018+
llvm_unreachable("Itanium ABI does not use vector deleting dtors");
60176019
}
60186020
}
60196021

clang/lib/AST/MicrosoftMangle.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,8 +1484,9 @@ void MicrosoftCXXNameMangler::mangleCXXDtorType(CXXDtorType T) {
14841484
// <operator-name> ::= ?_G # scalar deleting destructor
14851485
case Dtor_Deleting: Out << "?_G"; return;
14861486
// <operator-name> ::= ?_E # vector deleting destructor
1487-
// FIXME: Add a vector deleting dtor type. It goes in the vtable, so we need
1488-
// it.
1487+
case Dtor_VectorDeleting:
1488+
Out << "?_E";
1489+
return;
14891490
case Dtor_Comdat:
14901491
llvm_unreachable("not expecting a COMDAT");
14911492
}
@@ -2886,9 +2887,12 @@ void MicrosoftCXXNameMangler::mangleFunctionType(const FunctionType *T,
28862887
// ::= @ # structors (they have no declared return type)
28872888
if (IsStructor) {
28882889
if (isa<CXXDestructorDecl>(D) && isStructorDecl(D)) {
2889-
// The scalar deleting destructor takes an extra int argument which is not
2890-
// reflected in the AST.
2891-
if (StructorType == Dtor_Deleting) {
2890+
// The deleting destructors take an extra argument of type int that
2891+
// indicates whether the storage for the object should be deleted and
2892+
// whether a single object or an array of objects is being destroyed. This
2893+
// extra argument is not reflected in the AST.
2894+
if (StructorType == Dtor_Deleting ||
2895+
StructorType == Dtor_VectorDeleting) {
28922896
Out << (PointersAre64Bit ? "PEAXI@Z" : "PAXI@Z");
28932897
return;
28942898
}
@@ -3861,10 +3865,10 @@ void MicrosoftMangleContextImpl::mangleCXXDtorThunk(const CXXDestructorDecl *DD,
38613865
const ThunkInfo &Thunk,
38623866
bool /*ElideOverrideInfo*/,
38633867
raw_ostream &Out) {
3864-
// FIXME: Actually, the dtor thunk should be emitted for vector deleting
3865-
// dtors rather than scalar deleting dtors. Just use the vector deleting dtor
3866-
// mangling manually until we support both deleting dtor types.
3867-
assert(Type == Dtor_Deleting);
3868+
// The dtor thunk should use vector deleting dtor mangling, however as an
3869+
// optimization we may end up emitting only scalar deleting dtor body, so just
3870+
// use the vector deleting dtor mangling manually.
3871+
assert(Type == Dtor_Deleting || Type == Dtor_VectorDeleting);
38683872
msvc_hashing_ostream MHO(Out);
38693873
MicrosoftCXXNameMangler Mangler(*this, MHO, DD, Type);
38703874
Mangler.getStream() << "??_E";

clang/lib/AST/VTableBuilder.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,8 +1735,8 @@ void ItaniumVTableBuilder::LayoutPrimaryAndSecondaryVTables(
17351735
const CXXMethodDecl *MD = I.first;
17361736
const MethodInfo &MI = I.second;
17371737
if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
1738-
MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)]
1739-
= MI.VTableIndex - AddressPoint;
1738+
MethodVTableIndices[GlobalDecl(DD, Dtor_Complete)] =
1739+
MI.VTableIndex - AddressPoint;
17401740
MethodVTableIndices[GlobalDecl(DD, Dtor_Deleting)]
17411741
= MI.VTableIndex + 1 - AddressPoint;
17421742
} else {
@@ -2657,7 +2657,11 @@ class VFTableBuilder {
26572657
MethodVFTableLocation Loc(MI.VBTableIndex, WhichVFPtr.getVBaseWithVPtr(),
26582658
WhichVFPtr.NonVirtualOffset, MI.VFTableIndex);
26592659
if (const CXXDestructorDecl *DD = dyn_cast<CXXDestructorDecl>(MD)) {
2660-
MethodVFTableLocations[GlobalDecl(DD, Dtor_Deleting)] = Loc;
2660+
// In Microsoft ABI vftable always references vector deleting dtor.
2661+
CXXDtorType DtorTy = Context.getTargetInfo().getCXXABI().isMicrosoft()
2662+
? Dtor_VectorDeleting
2663+
: Dtor_Deleting;
2664+
MethodVFTableLocations[GlobalDecl(DD, DtorTy)] = Loc;
26612665
} else {
26622666
MethodVFTableLocations[MD] = Loc;
26632667
}
@@ -3287,7 +3291,10 @@ void VFTableBuilder::dumpLayout(raw_ostream &Out) {
32873291
const CXXDestructorDecl *DD = Component.getDestructorDecl();
32883292

32893293
DD->printQualifiedName(Out);
3290-
Out << "() [scalar deleting]";
3294+
if (Context.getTargetInfo().getCXXABI().isMicrosoft())
3295+
Out << "() [vector deleting]";
3296+
else
3297+
Out << "() [scalar deleting]";
32913298

32923299
if (DD->isPureVirtual())
32933300
Out << " [pure]";
@@ -3758,7 +3765,7 @@ void MicrosoftVTableContext::dumpMethodLocations(
37583765
PredefinedIdentKind::PrettyFunctionNoVirtual, MD);
37593766

37603767
if (isa<CXXDestructorDecl>(MD)) {
3761-
IndicesMap[I.second] = MethodName + " [scalar deleting]";
3768+
IndicesMap[I.second] = MethodName + " [vector deleting]";
37623769
} else {
37633770
IndicesMap[I.second] = MethodName;
37643771
}
@@ -3875,7 +3882,7 @@ MicrosoftVTableContext::getMethodVFTableLocation(GlobalDecl GD) {
38753882
assert(hasVtableSlot(cast<CXXMethodDecl>(GD.getDecl())) &&
38763883
"Only use this method for virtual methods or dtors");
38773884
if (isa<CXXDestructorDecl>(GD.getDecl()))
3878-
assert(GD.getDtorType() == Dtor_Deleting);
3885+
assert(GD.getDtorType() == Dtor_VectorDeleting);
38793886

38803887
GD = GD.getCanonicalDecl();
38813888

clang/lib/CodeGen/CGCXX.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
175175
// requires explicit comdat support in the IL.
176176
if (llvm::GlobalValue::isWeakForLinker(TargetLinkage))
177177
return true;
178-
179178
// Create the alias with no name.
180179
auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
181180
Aliasee, &getModule());
@@ -201,6 +200,42 @@ bool CodeGenModule::TryEmitBaseDestructorAsAlias(const CXXDestructorDecl *D) {
201200
return false;
202201
}
203202

203+
/// Emit a definition as a global alias for another definition, unconditionally.
204+
void CodeGenModule::EmitDefinitionAsAlias(GlobalDecl AliasDecl,
205+
GlobalDecl TargetDecl) {
206+
207+
llvm::Type *AliasValueType = getTypes().GetFunctionType(AliasDecl);
208+
209+
StringRef MangledName = getMangledName(AliasDecl);
210+
llvm::GlobalValue *Entry = GetGlobalValue(MangledName);
211+
if (Entry && !Entry->isDeclaration())
212+
return;
213+
auto *Aliasee = cast<llvm::GlobalValue>(GetAddrOfGlobal(TargetDecl));
214+
215+
// Determine the linkage type for the alias.
216+
llvm::GlobalValue::LinkageTypes Linkage = getFunctionLinkage(AliasDecl);
217+
218+
// Create the alias with no name.
219+
auto *Alias = llvm::GlobalAlias::create(AliasValueType, 0, Linkage, "",
220+
Aliasee, &getModule());
221+
// Destructors are always unnamed_addr.
222+
Alias->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
223+
224+
if (Entry) {
225+
assert(Entry->getValueType() == AliasValueType &&
226+
Entry->getAddressSpace() == Alias->getAddressSpace() &&
227+
"declaration exists with different type");
228+
Alias->takeName(Entry);
229+
Entry->replaceAllUsesWith(Alias);
230+
Entry->eraseFromParent();
231+
} else {
232+
Alias->setName(MangledName);
233+
}
234+
235+
// Set any additional necessary attributes for the alias.
236+
SetCommonAttributes(AliasDecl, Alias);
237+
}
238+
204239
llvm::Function *CodeGenModule::codegenCXXStructor(GlobalDecl GD) {
205240
const CGFunctionInfo &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD);
206241
auto *Fn = cast<llvm::Function>(

clang/lib/CodeGen/CGCXXABI.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,20 @@ void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
273273
numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
274274
}
275275

276+
void CGCXXABI::ReadArrayCookie(CodeGenFunction &CGF, Address ptr,
277+
QualType eltTy, llvm::Value *&numElements,
278+
llvm::Value *&allocPtr, CharUnits &cookieSize) {
279+
assert(eltTy.isDestructedType());
280+
281+
// Derive a char* in the same address space as the pointer.
282+
ptr = ptr.withElementType(CGF.Int8Ty);
283+
284+
cookieSize = getArrayCookieSizeImpl(eltTy);
285+
Address allocAddr = CGF.Builder.CreateConstInBoundsByteGEP(ptr, -cookieSize);
286+
allocPtr = allocAddr.emitRawPointer(CGF);
287+
numElements = readArrayCookieImpl(CGF, allocAddr, cookieSize);
288+
}
289+
276290
llvm::Value *CGCXXABI::readArrayCookieImpl(CodeGenFunction &CGF,
277291
Address ptr,
278292
CharUnits cookieSize) {

clang/lib/CodeGen/CGCXXABI.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ class CGCXXABI {
275275
virtual CatchTypeInfo getCatchAllTypeInfo();
276276

277277
virtual bool shouldTypeidBeNullChecked(QualType SrcRecordTy) = 0;
278+
virtual bool hasVectorDeletingDtors() = 0;
278279
virtual void EmitBadTypeidCall(CodeGenFunction &CGF) = 0;
279280
virtual llvm::Value *EmitTypeid(CodeGenFunction &CGF, QualType SrcRecordTy,
280281
Address ThisPtr,
@@ -575,6 +576,12 @@ class CGCXXABI {
575576
QualType ElementType, llvm::Value *&NumElements,
576577
llvm::Value *&AllocPtr, CharUnits &CookieSize);
577578

579+
/// Reads the array cookie associated with the given pointer,
580+
/// that should have one.
581+
void ReadArrayCookie(CodeGenFunction &CGF, Address Ptr, QualType ElementType,
582+
llvm::Value *&NumElements, llvm::Value *&AllocPtr,
583+
CharUnits &CookieSize);
584+
578585
/// Return whether the given global decl needs a VTT parameter.
579586
virtual bool NeedsVTTParameter(GlobalDecl GD);
580587

clang/lib/CodeGen/CGClass.cpp

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,70 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF,
14331433
return true;
14341434
}
14351435

1436+
static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD,
1437+
CodeGenFunction &CGF,
1438+
llvm::Value *ShouldDeleteCondition) {
1439+
Address ThisPtr = CGF.LoadCXXThisAddress();
1440+
llvm::BasicBlock *ScalarBB = CGF.createBasicBlock("dtor.scalar");
1441+
llvm::BasicBlock *callDeleteBB =
1442+
CGF.createBasicBlock("dtor.call_delete_after_array_destroy");
1443+
llvm::BasicBlock *VectorBB = CGF.createBasicBlock("dtor.vector");
1444+
auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
1445+
llvm::Value *CheckTheBitForArrayDestroy = CGF.Builder.CreateAnd(
1446+
ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 2));
1447+
llvm::Value *ShouldDestroyArray =
1448+
CGF.Builder.CreateIsNull(CheckTheBitForArrayDestroy);
1449+
CGF.Builder.CreateCondBr(ShouldDestroyArray, ScalarBB, VectorBB);
1450+
1451+
CGF.EmitBlock(VectorBB);
1452+
1453+
llvm::Value *numElements = nullptr;
1454+
llvm::Value *allocatedPtr = nullptr;
1455+
CharUnits cookieSize;
1456+
QualType EltTy = DD->getThisType()->getPointeeType();
1457+
CGF.CGM.getCXXABI().ReadArrayCookie(CGF, ThisPtr, EltTy, numElements,
1458+
allocatedPtr, cookieSize);
1459+
1460+
// Destroy the elements.
1461+
QualType::DestructionKind dtorKind = EltTy.isDestructedType();
1462+
1463+
assert(dtorKind);
1464+
assert(numElements && "no element count for a type with a destructor!");
1465+
1466+
CharUnits elementSize = CGF.getContext().getTypeSizeInChars(EltTy);
1467+
CharUnits elementAlign =
1468+
ThisPtr.getAlignment().alignmentOfArrayElement(elementSize);
1469+
1470+
llvm::Value *arrayBegin = ThisPtr.emitRawPointer(CGF);
1471+
llvm::Value *arrayEnd = CGF.Builder.CreateInBoundsGEP(
1472+
ThisPtr.getElementType(), arrayBegin, numElements, "delete.end");
1473+
1474+
// We already checked that the array is not 0-length before entering vector
1475+
// deleting dtor.
1476+
CGF.emitArrayDestroy(arrayBegin, arrayEnd, EltTy, elementAlign,
1477+
CGF.getDestroyer(dtorKind),
1478+
/*checkZeroLength*/ false, CGF.needsEHCleanup(dtorKind));
1479+
1480+
llvm::BasicBlock *VectorBBCont = CGF.createBasicBlock("dtor.vector.cont");
1481+
CGF.EmitBlock(VectorBBCont);
1482+
1483+
llvm::Value *CheckTheBitForDeleteCall = CGF.Builder.CreateAnd(
1484+
ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 1));
1485+
1486+
llvm::Value *ShouldCallDelete =
1487+
CGF.Builder.CreateIsNull(CheckTheBitForDeleteCall);
1488+
CGF.Builder.CreateCondBr(ShouldCallDelete, CGF.ReturnBlock.getBlock(),
1489+
callDeleteBB);
1490+
CGF.EmitBlock(callDeleteBB);
1491+
const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl);
1492+
const CXXRecordDecl *ClassDecl = Dtor->getParent();
1493+
CGF.EmitDeleteCall(Dtor->getOperatorDelete(), allocatedPtr,
1494+
CGF.getContext().getTagDeclType(ClassDecl));
1495+
1496+
CGF.EmitBranchThroughCleanup(CGF.ReturnBlock);
1497+
CGF.EmitBlock(ScalarBB);
1498+
}
1499+
14361500
/// EmitDestructorBody - Emits the body of the current destructor.
14371501
void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
14381502
const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CurGD.getDecl());
@@ -1462,7 +1526,9 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
14621526
// outside of the function-try-block, which means it's always
14631527
// possible to delegate the destructor body to the complete
14641528
// destructor. Do so.
1465-
if (DtorType == Dtor_Deleting) {
1529+
if (DtorType == Dtor_Deleting || DtorType == Dtor_VectorDeleting) {
1530+
if (CXXStructorImplicitParamValue && DtorType == Dtor_VectorDeleting)
1531+
EmitConditionalArrayDtorCall(Dtor, *this, CXXStructorImplicitParamValue);
14661532
RunCleanupsScope DtorEpilogue(*this);
14671533
EnterDtorCleanups(Dtor, Dtor_Deleting);
14681534
if (HaveInsertPoint()) {
@@ -1491,6 +1557,8 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) {
14911557
switch (DtorType) {
14921558
case Dtor_Comdat: llvm_unreachable("not expecting a COMDAT");
14931559
case Dtor_Deleting: llvm_unreachable("already handled deleting case");
1560+
case Dtor_VectorDeleting:
1561+
llvm_unreachable("already handled vector deleting case");
14941562

14951563
case Dtor_Complete:
14961564
assert((Body || getTarget().getCXXABI().isMicrosoft()) &&
@@ -1573,7 +1641,6 @@ namespace {
15731641
return CGF.EmitScalarExpr(ThisArg);
15741642
return CGF.LoadCXXThis();
15751643
}
1576-
15771644
/// Call the operator delete associated with the current destructor.
15781645
struct CallDtorDelete final : EHScopeStack::Cleanup {
15791646
CallDtorDelete() {}
@@ -1592,8 +1659,10 @@ namespace {
15921659
bool ReturnAfterDelete) {
15931660
llvm::BasicBlock *callDeleteBB = CGF.createBasicBlock("dtor.call_delete");
15941661
llvm::BasicBlock *continueBB = CGF.createBasicBlock("dtor.continue");
1595-
llvm::Value *ShouldCallDelete
1596-
= CGF.Builder.CreateIsNull(ShouldDeleteCondition);
1662+
auto *CondTy = cast<llvm::IntegerType>(ShouldDeleteCondition->getType());
1663+
llvm::Value *CheckTheBit = CGF.Builder.CreateAnd(
1664+
ShouldDeleteCondition, llvm::ConstantInt::get(CondTy, 1));
1665+
llvm::Value *ShouldCallDelete = CGF.Builder.CreateIsNull(CheckTheBit);
15971666
CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB);
15981667

15991668
CGF.EmitBlock(callDeleteBB);

clang/lib/CodeGen/CGDebugInfo.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2119,7 +2119,8 @@ llvm::DISubprogram *CGDebugInfo::CreateCXXMemberFunction(
21192119
// Emit MS ABI vftable information. There is only one entry for the
21202120
// deleting dtor.
21212121
const auto *DD = dyn_cast<CXXDestructorDecl>(Method);
2122-
GlobalDecl GD = DD ? GlobalDecl(DD, Dtor_Deleting) : GlobalDecl(Method);
2122+
GlobalDecl GD =
2123+
DD ? GlobalDecl(DD, Dtor_VectorDeleting) : GlobalDecl(Method);
21232124
MethodVFTableLocation ML =
21242125
CGM.getMicrosoftVTableContext().getMethodVFTableLocation(GD);
21252126
VIndex = ML.Index;

0 commit comments

Comments
 (0)