diff options
-rw-r--r-- | clang/include/clang/Basic/TargetCXXABI.h | 6 | ||||
-rw-r--r-- | clang/lib/AST/MicrosoftMangle.cpp | 27 | ||||
-rw-r--r-- | clang/lib/CodeGen/CGCXX.cpp | 35 | ||||
-rw-r--r-- | clang/lib/CodeGen/CGCXXABI.h | 9 | ||||
-rw-r--r-- | clang/lib/CodeGen/CGClass.cpp | 11 | ||||
-rw-r--r-- | clang/lib/CodeGen/CGExprCXX.cpp | 4 | ||||
-rw-r--r-- | clang/lib/CodeGen/CodeGenModule.cpp | 16 | ||||
-rw-r--r-- | clang/lib/CodeGen/CodeGenModule.h | 15 | ||||
-rw-r--r-- | clang/lib/CodeGen/ItaniumCXXABI.cpp | 26 | ||||
-rw-r--r-- | clang/lib/CodeGen/MicrosoftCXXABI.cpp | 48 | ||||
-rw-r--r-- | clang/test/CodeGenCXX/microsoft-abi-structors.cpp | 79 |
11 files changed, 207 insertions, 69 deletions
diff --git a/clang/include/clang/Basic/TargetCXXABI.h b/clang/include/clang/Basic/TargetCXXABI.h index b04fab866f2..1590cca6233 100644 --- a/clang/include/clang/Basic/TargetCXXABI.h +++ b/clang/include/clang/Basic/TargetCXXABI.h @@ -152,12 +152,6 @@ public: return isItaniumFamily(); } - /// \brief Does this ABI have different entrypoints for complete-object - /// and base-subobject destructors? - bool hasDestructorVariants() const { - return isItaniumFamily(); - } - /// \brief Does this ABI allow virtual bases to be primary base classes? bool hasPrimaryVBases() const { return isItaniumFamily(); diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp index 88548ae9918..90255da73ef 100644 --- a/clang/lib/AST/MicrosoftMangle.cpp +++ b/clang/lib/AST/MicrosoftMangle.cpp @@ -522,9 +522,9 @@ MicrosoftCXXNameMangler::mangleUnqualifiedName(const NamedDecl *ND, // use the type we were given. mangleCXXDtorType(static_cast<CXXDtorType>(StructorType)); else - // Otherwise, use the complete destructor name. This is relevant if a + // Otherwise, use the base destructor name. This is relevant if a // class with a destructor is declared within a destructor. - mangleCXXDtorType(Dtor_Complete); + mangleCXXDtorType(Dtor_Base); break; case DeclarationName::CXXConversionFunctionName: @@ -594,18 +594,19 @@ void MicrosoftCXXNameMangler::manglePostfix(const DeclContext *DC, } void MicrosoftCXXNameMangler::mangleCXXDtorType(CXXDtorType T) { + // Microsoft uses the names on the case labels for these dtor variants. Clang + // uses the Itanium terminology internally. Everything in this ABI delegates + // towards the base dtor. switch (T) { - case Dtor_Deleting: - Out << "?_G"; - return; - case Dtor_Base: - // FIXME: We should be asked to mangle base dtors. - // However, fixing this would require larger changes to the CodeGenModule. - // Please put llvm_unreachable here when CGM is changed. - // For now, just mangle a base dtor the same way as a complete dtor... - case Dtor_Complete: - Out << "?1"; - return; + // <operator-name> ::= ?1 # destructor + case Dtor_Base: Out << "?1"; return; + // <operator-name> ::= ?_D # vbase destructor + case Dtor_Complete: Out << "?_D"; return; + // <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. } llvm_unreachable("Unsupported dtor type?"); } diff --git a/clang/lib/CodeGen/CGCXX.cpp b/clang/lib/CodeGen/CGCXX.cpp index b2a464e1cb1..6023bf7cc7d 100644 --- a/clang/lib/CodeGen/CGCXX.cpp +++ b/clang/lib/CodeGen/CGCXX.cpp @@ -228,22 +228,6 @@ CodeGenModule::GetAddrOfCXXConstructor(const CXXConstructorDecl *ctor, /*ForVTable=*/false)); } -void CodeGenModule::EmitCXXDestructors(const CXXDestructorDecl *D) { - // The destructor in a virtual table is always a 'deleting' - // destructor, which calls the complete destructor and then uses the - // appropriate operator delete. - if (D->isVirtual()) - EmitGlobal(GlobalDecl(D, Dtor_Deleting)); - - // The destructor used for destructing this as a most-derived class; - // call the base destructor and then destructs any virtual bases. - EmitGlobal(GlobalDecl(D, Dtor_Complete)); - - // The destructor used for destructing this as a base class; ignores - // virtual bases. - EmitGlobal(GlobalDecl(D, Dtor_Base)); -} - void CodeGenModule::EmitCXXDestructor(const CXXDestructorDecl *dtor, CXXDtorType dtorType) { // The complete destructor is equivalent to the base destructor for @@ -277,16 +261,27 @@ void CodeGenModule::EmitCXXDestructor(const CXXDestructorDecl *dtor, llvm::GlobalValue * CodeGenModule::GetAddrOfCXXDestructor(const CXXDestructorDecl *dtor, CXXDtorType dtorType, - const CGFunctionInfo *fnInfo) { + const CGFunctionInfo *fnInfo, + llvm::FunctionType *fnType) { + // If the class has no virtual bases, then the complete and base destructors + // are equivalent, for all C++ ABIs supported by clang. We can save on code + // size by calling the base dtor directly, especially if we'd have to emit a + // thunk otherwise. + // FIXME: We should do this for Itanium, after verifying that nothing breaks. + if (dtorType == Dtor_Complete && dtor->getParent()->getNumVBases() == 0 && + getCXXABI().useThunkForDtorVariant(dtor, Dtor_Complete)) + dtorType = Dtor_Base; + GlobalDecl GD(dtor, dtorType); StringRef name = getMangledName(GD); if (llvm::GlobalValue *existing = GetGlobalValue(name)) return existing; - if (!fnInfo) fnInfo = &getTypes().arrangeCXXDestructor(dtor, dtorType); - - llvm::FunctionType *fnType = getTypes().GetFunctionType(*fnInfo); + if (!fnType) { + if (!fnInfo) fnInfo = &getTypes().arrangeCXXDestructor(dtor, dtorType); + fnType = getTypes().GetFunctionType(*fnInfo); + } return cast<llvm::Function>(GetOrCreateLLVMFunction(name, fnType, GD, /*ForVTable=*/false)); } diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h index 6629e045749..27792d02c68 100644 --- a/clang/lib/CodeGen/CGCXXABI.h +++ b/clang/lib/CodeGen/CGCXXABI.h @@ -244,6 +244,15 @@ public: CanQualType &ResTy, SmallVectorImpl<CanQualType> &ArgTys) = 0; + /// Returns true if the given destructor type should be emitted as a linkonce + /// delegating thunk, regardless of whether the dtor is defined in this TU or + /// not. + virtual bool useThunkForDtorVariant(const CXXDestructorDecl *Dtor, + CXXDtorType DT) const = 0; + + /// Emit destructor variants required by this ABI. + virtual void EmitCXXDestructors(const CXXDestructorDecl *D) = 0; + /// Build the ABI-specific portion of the parameter list for a /// function. This generally involves a 'this' parameter and /// possibly some extra data for constructors and destructors. diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 2bb44552e26..dd75523d14a 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -1265,16 +1265,19 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) { // If this is the complete variant, just invoke the base variant; // the epilogue will destruct the virtual bases. But we can't do // this optimization if the body is a function-try-block, because - // we'd introduce *two* handler blocks. + // we'd introduce *two* handler blocks. In the Microsoft ABI, we + // always delegate because we might not have a definition in this TU. switch (DtorType) { case Dtor_Deleting: llvm_unreachable("already handled deleting case"); case Dtor_Complete: + assert((Body || getTarget().getCXXABI().isMicrosoft()) && + "can't emit a dtor without a body for non-Microsoft ABIs"); + // Enter the cleanup scopes for virtual bases. EnterDtorCleanups(Dtor, Dtor_Complete); - if (!isTryBody && - CGM.getTarget().getCXXABI().hasDestructorVariants()) { + if (!isTryBody) { EmitCXXDestructorCall(Dtor, Dtor_Base, /*ForVirtualBase=*/false, /*Delegating=*/false, LoadCXXThis()); break; @@ -1282,6 +1285,8 @@ void CodeGenFunction::EmitDestructorBody(FunctionArgList &Args) { // Fallthrough: act like we're in the base variant. case Dtor_Base: + assert(Body); + // Enter the cleanup scopes for fields and non-virtual bases. EnterDtorCleanups(Dtor, Dtor_Base); diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 1520656c9b8..c74d88b9508 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -260,7 +260,7 @@ RValue CodeGenFunction::EmitCXXMemberCallExpr(const CXXMemberCallExpr *CE, else FInfo = &CGM.getTypes().arrangeCXXMethodDeclaration(CalleeDecl); - llvm::Type *Ty = CGM.getTypes().GetFunctionType(*FInfo); + llvm::FunctionType *Ty = CGM.getTypes().GetFunctionType(*FInfo); // C++ [class.virtual]p12: // Explicit qualification with the scope operator (5.1) suppresses the @@ -284,7 +284,7 @@ RValue CodeGenFunction::EmitCXXMemberCallExpr(const CXXMemberCallExpr *CE, ME->hasQualifier()) Callee = BuildAppleKextVirtualCall(MD, ME->getQualifier(), Ty); else if (!DevirtualizedMethod) - Callee = CGM.GetAddrOfFunction(GlobalDecl(Dtor, Dtor_Complete), Ty); + Callee = CGM.GetAddrOfCXXDestructor(Dtor, Dtor_Complete, FInfo, Ty); else { const CXXDestructorDecl *DDtor = cast<CXXDestructorDecl>(DevirtualizedMethod); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 078edebb965..fd884db6ca2 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -513,6 +513,12 @@ void CodeGenModule::EmitCtorList(const CtorList &Fns, const char *GlobalName) { llvm::GlobalValue::LinkageTypes CodeGenModule::getFunctionLinkage(GlobalDecl GD) { const FunctionDecl *D = cast<FunctionDecl>(GD.getDecl()); + + if (isa<CXXDestructorDecl>(D) && + getCXXABI().useThunkForDtorVariant(cast<CXXDestructorDecl>(D), + GD.getDtorType())) + return llvm::Function::LinkOnceODRLinkage; + GVALinkage Linkage = getContext().GetGVALinkageForFunction(D); if (Linkage == GVA_Internal) @@ -1348,6 +1354,14 @@ CodeGenModule::GetOrCreateLLVMFunction(StringRef MangledName, return llvm::ConstantExpr::getBitCast(Entry, Ty->getPointerTo()); } + // All MSVC dtors other than the base dtor are linkonce_odr and delegate to + // each other bottoming out with the base dtor. Therefore we emit non-base + // dtors on usage, even if there is no dtor definition in the TU. + if (D && isa<CXXDestructorDecl>(D) && + getCXXABI().useThunkForDtorVariant(cast<CXXDestructorDecl>(D), + GD.getDtorType())) + DeferredDeclsToEmit.push_back(GD); + // This function doesn't have a complete type (for example, the return // type is an incomplete struct). Use a fake type instead, and make // sure not to try to set attributes. @@ -2869,7 +2883,7 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) { case Decl::CXXDestructor: if (cast<FunctionDecl>(D)->isLateTemplateParsed()) return; - EmitCXXDestructors(cast<CXXDestructorDecl>(D)); + getCXXABI().EmitCXXDestructors(cast<CXXDestructorDecl>(D)); break; case Decl::StaticAssert: diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index c2cbe99a668..c99a0ecfc7e 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -752,7 +752,8 @@ public: /// given type. llvm::GlobalValue *GetAddrOfCXXDestructor(const CXXDestructorDecl *dtor, CXXDtorType dtorType, - const CGFunctionInfo *fnInfo = 0); + const CGFunctionInfo *fnInfo = 0, + llvm::FunctionType *fnType = 0); /// getBuiltinLibFunction - Given a builtin id for a function like /// "__builtin_fabsf", return a Function* for "fabsf". @@ -980,6 +981,10 @@ public: DeferredVTables.push_back(RD); } + /// EmitGlobal - Emit code for a singal global function or var decl. Forward + /// declarations are emitted lazily. + void EmitGlobal(GlobalDecl D); + private: llvm::GlobalValue *GetGlobalValue(StringRef Ref); @@ -1011,10 +1016,6 @@ private: llvm::Function *F, bool IsIncompleteFunction); - /// EmitGlobal - Emit code for a singal global function or var decl. Forward - /// declarations are emitted lazily. - void EmitGlobal(GlobalDecl D); - void EmitGlobalDefinition(GlobalDecl D); void EmitGlobalFunctionDefinition(GlobalDecl GD); @@ -1040,10 +1041,6 @@ private: /// a C++ constructor Decl. void EmitCXXConstructor(const CXXConstructorDecl *D, CXXCtorType Type); - /// EmitCXXDestructors - Emit destructors (base, complete) from a - /// C++ destructor Decl. - void EmitCXXDestructors(const CXXDestructorDecl *D); - /// EmitCXXDestructor - Emit a single destructor with the given type from /// a C++ destructor Decl. void EmitCXXDestructor(const CXXDestructorDecl *D, CXXDtorType Type); diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index cdfe909f9d0..55814b455bb 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -113,6 +113,16 @@ public: CanQualType &ResTy, SmallVectorImpl<CanQualType> &ArgTys); + bool useThunkForDtorVariant(const CXXDestructorDecl *Dtor, + CXXDtorType DT) const { + // Itanium does not emit any destructor variant as an inline thunk. + // Delegating may occur as an optimization, but all variants are either + // emitted with external linkage or as linkonce if they are inline and used. + return false; + } + + void EmitCXXDestructors(const CXXDestructorDecl *D); + void BuildInstanceFunctionParams(CodeGenFunction &CGF, QualType &ResTy, FunctionArgList &Params); @@ -761,6 +771,22 @@ void ItaniumCXXABI::BuildDestructorSignature(const CXXDestructorDecl *Dtor, ArgTys.push_back(Context.getPointerType(Context.VoidPtrTy)); } +void ItaniumCXXABI::EmitCXXDestructors(const CXXDestructorDecl *D) { + // The destructor in a virtual table is always a 'deleting' + // destructor, which calls the complete destructor and then uses the + // appropriate operator delete. + if (D->isVirtual()) + CGM.EmitGlobal(GlobalDecl(D, Dtor_Deleting)); + + // The destructor used for destructing this as a most-derived class; + // call the base destructor and then destructs any virtual bases. + CGM.EmitGlobal(GlobalDecl(D, Dtor_Complete)); + + // The destructor used for destructing this as a base class; ignores + // virtual bases. + CGM.EmitGlobal(GlobalDecl(D, Dtor_Base)); +} + void ItaniumCXXABI::BuildInstanceFunctionParams(CodeGenFunction &CGF, QualType &ResTy, FunctionArgList &Params) { diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index 22d548857b7..0e01d8bacfc 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -65,11 +65,51 @@ public: llvm::BasicBlock *EmitCtorCompleteObjectHandler(CodeGenFunction &CGF, const CXXRecordDecl *RD); - void BuildDestructorSignature(const CXXDestructorDecl *Ctor, + // Background on MSVC destructors + // ============================== + // + // Both Itanium and MSVC ABIs have destructor variants. The variant names + // roughly correspond in the following way: + // Itanium Microsoft + // Base -> no name, just ~Class + // Complete -> vbase destructor + // Deleting -> scalar deleting destructor + // vector deleting destructor + // + // The base and complete destructors are the same as in Itanium, although the + // complete destructor does not accept a VTT parameter when there are virtual + // bases. A separate mechanism involving vtordisps is used to ensure that + // virtual methods of destroyed subobjects are not called. + // + // The deleting destructors accept an i32 bitfield as a second parameter. Bit + // 1 indicates if the memory should be deleted. Bit 2 indicates if the this + // pointer points to an array. The scalar deleting destructor assumes that + // bit 2 is zero, and therefore does not contain a loop. + // + // For virtual destructors, only one entry is reserved in the vftable, and it + // always points to the vector deleting destructor. The vector deleting + // destructor is the most general, so it can be used to destroy objects in + // place, delete single heap objects, or delete arrays. + // + // A TU defining a non-inline destructor is only guaranteed to emit a base + // destructor, and all of the other variants are emitted on an as-needed basis + // in COMDATs. Because a non-base destructor can be emitted in a TU that + // lacks a definition for the destructor, non-base destructors must always + // delegate to or alias the base destructor. + + void BuildDestructorSignature(const CXXDestructorDecl *Dtor, CXXDtorType Type, CanQualType &ResTy, SmallVectorImpl<CanQualType> &ArgTys); + /// Non-base dtors should be emitted as delegating thunks in this ABI. + bool useThunkForDtorVariant(const CXXDestructorDecl *Dtor, + CXXDtorType DT) const { + return DT != Dtor_Base; + } + + void EmitCXXDestructors(const CXXDestructorDecl *D); + void BuildInstanceFunctionParams(CodeGenFunction &CGF, QualType &ResTy, FunctionArgList &Params); @@ -387,6 +427,12 @@ void MicrosoftCXXABI::BuildDestructorSignature(const CXXDestructorDecl *Dtor, } } +void MicrosoftCXXABI::EmitCXXDestructors(const CXXDestructorDecl *D) { + // The TU defining a dtor is only guaranteed to emit a base destructor. All + // other destructor variants are delegating thunks. + CGM.EmitGlobal(GlobalDecl(D, Dtor_Base)); +} + static bool IsDeletingDtor(GlobalDecl GD) { const CXXMethodDecl* MD = cast<CXXMethodDecl>(GD.getDecl()); if (isa<CXXDestructorDecl>(MD)) { diff --git a/clang/test/CodeGenCXX/microsoft-abi-structors.cpp b/clang/test/CodeGenCXX/microsoft-abi-structors.cpp index 6d43c7daf1e..24be897b057 100644 --- a/clang/test/CodeGenCXX/microsoft-abi-structors.cpp +++ b/clang/test/CodeGenCXX/microsoft-abi-structors.cpp @@ -1,15 +1,15 @@ -// RUN: %clang_cc1 -emit-llvm %s -o - -cxx-abi microsoft -triple=i386-pc-win32 -fno-rtti > %t 2>&1 +// RUN: %clang_cc1 -emit-llvm %s -o - -cxx-abi microsoft -triple=i386-pc-win32 -fno-rtti > %t // RUN: FileCheck %s < %t -// Using a different check prefix as the inline destructors might be placed -// anywhere in the output. -// RUN: FileCheck --check-prefix=DTORS %s < %t +// vftables are emitted very late, so do another pass to try to keep the checks +// in source order. +// RUN: FileCheck --check-prefix DTORS %s < %t namespace basic { class A { public: A() { } - ~A() { } + ~A(); }; void no_constructor_destructor_infinite_recursion() { @@ -21,7 +21,9 @@ void no_constructor_destructor_infinite_recursion() { // CHECK-NEXT: [[T1:%[.0-9A-Z_a-z]+]] = load %"class.basic::A"** [[THIS_ADDR]] // CHECK-NEXT: ret %"class.basic::A"* [[T1]] // CHECK-NEXT: } +} +A::~A() { // Make sure that the destructor doesn't call itself: // CHECK: define {{.*}} @"\01??1A@basic@@QAE@XZ" // CHECK-NOT: call void @"\01??1A@basic@@QAE@XZ" @@ -40,12 +42,6 @@ B::B() { struct C { virtual ~C() { -// Complete destructor first: -// DTORS: define {{.*}} x86_thiscallcc void @"\01??1C@basic@@UAE@XZ"(%"struct.basic::C"* %this) - -// Then, the scalar deleting destructor (used in the vtable): -// FIXME: add a test that verifies that the out-of-line scalar deleting -// destructor is linkonce_odr too. // DTORS: define linkonce_odr x86_thiscallcc void @"\01??_GC@basic@@UAEPAXI@Z"(%"struct.basic::C"* %this, i1 zeroext %should_call_delete) // DTORS: %[[FROMBOOL:[0-9a-z]+]] = zext i1 %should_call_delete to i8 // DTORS-NEXT: store i8 %[[FROMBOOL]], i8* %[[SHOULD_DELETE_VAR:[0-9a-z._]+]], align 1 @@ -56,7 +52,7 @@ struct C { // // DTORS: [[CALL_DELETE_LABEL]] // DTORS-NEXT: %[[THIS_AS_VOID:[0-9a-z]+]] = bitcast %"struct.basic::C"* %[[THIS]] to i8* -// DTORS-NEXT: call void @"\01??3@YAXPAX@Z"(i8* %[[THIS_AS_VOID]]) [[NUW:#[0-9]+]] +// DTORS-NEXT: call void @"\01??3@YAXPAX@Z"(i8* %[[THIS_AS_VOID]]) // DTORS-NEXT: br label %[[CONTINUE_LABEL]] // // DTORS: [[CONTINUE_LABEL]] @@ -119,8 +115,6 @@ struct D { void use_D() { D c; } -// DTORS: attributes [[NUW]] = {{[{].*}} nounwind {{.*[}]}} - } // end namespace basic @@ -228,3 +222,60 @@ E::E() { } } // end namespace constructors + +namespace dtors { + +struct A { + ~A(); +}; + +void call_nv_complete(A *a) { + a->~A(); +// CHECK: define void @"\01?call_nv_complete@dtors@@YAXPAUA@1@@Z" +// CHECK: call x86_thiscallcc void @"\01??1A@dtors@@QAE@XZ" +// CHECK: ret +} + +// CHECK: declare x86_thiscallcc void @"\01??1A@dtors@@QAE@XZ" + +// Now try some virtual bases, where we need the complete dtor. + +struct B : virtual A { ~B(); }; +struct C : virtual A { ~C(); }; +struct D : B, C { ~D(); }; + +void call_vbase_complete(D *d) { + d->~D(); +// CHECK: define void @"\01?call_vbase_complete@dtors@@YAXPAUD@1@@Z" +// CHECK: call x86_thiscallcc void @"\01??_DD@dtors@@QAE@XZ"(%"struct.dtors::D"* %{{[^,]+}}) +// CHECK: ret +} + +// The complete dtor should call the base dtors for D and the vbase A (once). +// CHECK: define linkonce_odr x86_thiscallcc void @"\01??_DD@dtors@@QAE@XZ" +// CHECK-NOT: call +// CHECK: call x86_thiscallcc void @"\01??1D@dtors@@QAE@XZ" +// CHECK-NOT: call +// CHECK: call x86_thiscallcc void @"\01??1A@dtors@@QAE@XZ" +// CHECK-NOT: call +// CHECK: ret + +void destroy_d_complete() { + D d; +// CHECK: define void @"\01?destroy_d_complete@dtors@@YAXXZ" +// CHECK: call x86_thiscallcc void @"\01??_DD@dtors@@QAE@XZ"(%"struct.dtors::D"* %{{[^,]+}}) +// CHECK: ret +} + +// FIXME: Clang manually inlines the deletion, so we don't get a call to the +// deleting dtor (_G). The only way to call deleting dtors currently is through +// a vftable. +void call_nv_deleting_dtor(D *d) { + delete d; +// CHECK: define void @"\01?call_nv_deleting_dtor@dtors@@YAXPAUD@1@@Z" +// CHECK: call x86_thiscallcc void @"\01??_DD@dtors@@QAE@XZ"(%"struct.dtors::D"* %{{[^,]+}}) +// CHECK: call void @"\01??3@YAXPAX@Z" +// CHECK: ret +} + +} |