diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 46d51b469c27c..5edcd8f43319e 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -1033,6 +1033,17 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the automatically enables V3 unwind info (`-fwinx64-eh-unwind=v3`) if no explicit unwind version was specified. +- In MSVC compatibility mode, scalar and vector deleting destructors now call + ``__global_delete`` (or ``__global_array_delete`` for the array ``delete[]`` + path) instead of directly referencing ``::operator delete``. + This matches MSVC's behavior and fixes ``LNK2001`` linker errors in + environments where no global ``::operator delete`` exists. When the + translation unit contains a ``::delete`` expression, a ``__global_delete`` + forwarding body that calls ``::operator delete`` is emitted automatically. + Otherwise, if no body is emitted, an `/ALTERNATENAME` linker directive will + cause the linker to use the generated `__empty_global_delete` trap function + instead. + - Clang now supports `-std:c++26preview` for compatibility with MSVC. This enables C++26 features. #### LoongArch Support diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 9615620b2aaee..70666dffdc903 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -29,6 +29,7 @@ #include "llvm/IR/Intrinsics.h" #include "llvm/IR/Metadata.h" #include "llvm/Support/SaveAndRestore.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" #include "llvm/Transforms/Utils/SanitizerStats.h" #include @@ -1409,6 +1410,112 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF, return true; } +/// Get or create the MSVC-compatible __global_delete wrapper function. +/// +/// Destructor helpers call __global_delete instead of ::operator delete +/// directly. If this TU contains a ::delete expression (or a dllexport class +/// whose deleting destructor takes the global-delete path), a real forwarding +/// body is emitted at end-of-file. If ::delete is never used anywhere in the +/// program, then no definition will exist and the `/ALTERNATENAME` linker +/// directive will cause the linker to use __empty_global_delete as the +/// definition. __empty_global_delete is never expected to actually be called, +/// hence it is a trap function (a deliberate deviation from MSVC, whose empty +/// is a no-op). +/// +/// Array delete[] uses a parallel __global_array_delete wrapper, matching +/// MSVC. The scalar and array wrappers of a given signature share a single +/// __empty_global_delete fallback. +static llvm::Constant * +getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, + const FunctionDecl *GlobOD) { + assert(CGM.getTarget().getCXXABI().isMicrosoft() && + "__global_delete wrapper is only used with the Microsoft ABI"); + llvm::Module &M = CGM.getModule(); + llvm::LLVMContext &LLVMCtx = M.getContext(); + + llvm::Constant *GlobDeleteCallee = CGM.GetAddrOfFunction(GlobOD); + auto *GlobDeleteFn = cast(GlobDeleteCallee); + llvm::FunctionType *FnTy = GlobDeleteFn->getFunctionType(); + + // Derive the wrapper and empty-fallback mangled names. MSVC uses distinct + // wrapper names for scalar vs array global delete, but a single shared empty + // fallback per signature: + // Global ::operator delete mangling: ??3@ + // -> wrapper ?__global_delete@@ + // Global ::operator delete[] mangling: ??_V@ + // -> wrapper ?__global_array_delete@@ + // shared fallback: ?__empty_global_delete@@ + StringRef GlobDeleteMangledName = GlobDeleteFn->getName(); + StringRef Signature; + const char *WrapperBase; + if (GlobDeleteMangledName.starts_with("??3@")) { + Signature = GlobDeleteMangledName.substr(4); + WrapperBase = "?__global_delete@@"; + } else if (GlobDeleteMangledName.starts_with("??_V@")) { + Signature = GlobDeleteMangledName.substr(5); + WrapperBase = "?__global_array_delete@@"; + } else { + llvm_unreachable("unexpected global operator delete mangling"); + } + + std::string GlobalDeleteName = (WrapperBase + Signature).str(); + std::string EmptyGlobalDeleteName = + ("?__empty_global_delete@@" + Signature).str(); + + // Only set up the wrapper once per module. + if (llvm::Function *Existing = M.getFunction(GlobalDeleteName)) + return Existing; + + // Create the shared __empty_global_delete fallback if it doesn't already + // exist. The scalar and array wrappers of a given signature share one empty + // (matching MSVC, whose weak externals both point at a single + // __empty_global_delete). The body traps: this path is unreachable at + // runtime when ::delete is never used (a deliberate deviation from MSVC, + // whose empty is a no-op; see the doc comment above). + llvm::Function *EmptyFn = M.getFunction(EmptyGlobalDeleteName); + if (!EmptyFn) { + EmptyFn = llvm::Function::Create( + FnTy, llvm::GlobalValue::LinkOnceODRLinkage, EmptyGlobalDeleteName, &M); + EmptyFn->setComdat(M.getOrInsertComdat(EmptyGlobalDeleteName)); + EmptyFn->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + CGM.SetLLVMFunctionAttributes( + GlobalDecl(GlobOD), + CGM.getTypes().arrangeGlobalDeclaration(GlobalDecl(GlobOD)), EmptyFn, + /*IsThunk=*/false); + CGM.SetLLVMFunctionAttributesForDefinition(GlobOD, EmptyFn); + CGM.getTargetCodeGenInfo().setTargetAttributes(GlobOD, EmptyFn, CGM); + auto *BB = llvm::BasicBlock::Create(LLVMCtx, "", EmptyFn); + llvm::Function *TrapFn = + llvm::Intrinsic::getOrInsertDeclaration(&M, llvm::Intrinsic::trap); + auto *TrapCall = llvm::CallInst::Create(TrapFn, {}, "", BB); + TrapCall->setDoesNotReturn(); + TrapCall->setDoesNotThrow(); + new llvm::UnreachableInst(LLVMCtx, BB); + + // Nothing directly uses the empty other than the /alternatename directive, + // so explicitly mark it as used. + appendToUsed(M, {EmptyFn}); + } + + // Emit /ALTERNATENAME linker directive: if this wrapper isn't provided, + // fall back to the trapping __empty_global_delete. + std::string AltOption = + "/alternatename:" + GlobalDeleteName + "=" + EmptyGlobalDeleteName; + auto *AltMD = + llvm::MDNode::get(LLVMCtx, {llvm::MDString::get(LLVMCtx, AltOption)}); + M.getOrInsertNamedMetadata("llvm.linker.options")->addOperand(AltMD); + + // Return the __global_delete wrapper function to call. + auto GlobalDeleteCallee = M.getOrInsertFunction(GlobalDeleteName, FnTy); + auto *GlobalDeleteFn = cast(GlobalDeleteCallee.getCallee()); + + // Register this variant so we can emit a real forwarding body at end-of-TU + // if this TU contains any direct use of global ::operator delete. + CGM.addPendingGlobalDelete(GlobalDeleteFn, GlobOD); + + return GlobalDeleteFn; +} + static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, CodeGenFunction &CGF, llvm::Value *ShouldDeleteCondition) { @@ -1492,9 +1599,18 @@ static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, CGF.EmitBranchThroughCleanup(CGF.ReturnBlock); CGF.EmitBlock(GlobDelete); + // Use __global_delete wrapper instead of directly calling + // ::operator delete to match MSVC's behavior. See the doc comment on + // getOrCreateMSVCGlobalDeleteWrapper for details. + llvm::Constant *GlobalDeleteWrapper = getOrCreateMSVCGlobalDeleteWrapper( + CGF.CGM, Dtor->getGlobalArrayOperatorDelete()); + // For dllexport classes, emit forwarding bodies since the dtor is + // exported and another TU may not provide the forwarding body. + if (Dtor->hasAttr()) + CGF.CGM.noteDirectGlobalDelete(); CGF.EmitDeleteCall(Dtor->getGlobalArrayOperatorDelete(), allocatedPtr, CGF.getContext().getCanonicalTagType(ClassDecl), - numElements, cookieSize); + numElements, cookieSize, GlobalDeleteWrapper); } } else { // No operators delete[] were found, so emit a trap. @@ -1721,9 +1837,12 @@ void EmitConditionalDtorDeleteCall(CodeGenFunction &CGF, CGF.Builder.CreateCondBr(ShouldCallDelete, continueBB, callDeleteBB); CGF.EmitBlock(callDeleteBB); - auto EmitDeleteAndGoToEnd = [&](const FunctionDecl *DeleteOp) { + auto EmitDeleteAndGoToEnd = [&](const FunctionDecl *DeleteOp, + llvm::Constant *CalleeOverride = nullptr) { CGF.EmitDeleteCall(DeleteOp, LoadThisForDtorDelete(CGF, Dtor), - Context.getCanonicalTagType(ClassDecl)); + Context.getCanonicalTagType(ClassDecl), + /*NumElements=*/nullptr, /*CookieSize=*/CharUnits(), + CalleeOverride); if (ReturnAfterDelete) CGF.EmitBranchThroughCleanup(CGF.ReturnBlock); else @@ -1747,7 +1866,16 @@ void EmitConditionalDtorDeleteCall(CodeGenFunction &CGF, CGF.Builder.CreateCondBr(ShouldCallGlobDelete, ClassDelete, GlobDelete); CGF.EmitBlock(GlobDelete); - EmitDeleteAndGoToEnd(GlobOD); + // Use __global_delete wrapper instead of directly calling + // ::operator delete to match MSVC's behavior. See the doc comment on + // getOrCreateMSVCGlobalDeleteWrapper for details. + llvm::Constant *GlobalDeleteWrapper = + getOrCreateMSVCGlobalDeleteWrapper(CGF.CGM, GlobOD); + // For dllexport classes, emit forwarding bodies since the dtor is + // exported and another TU may not provide the forwarding body. + if (Dtor->hasAttr()) + CGF.CGM.noteDirectGlobalDelete(); + EmitDeleteAndGoToEnd(GlobOD, GlobalDeleteWrapper); CGF.EmitBlock(ClassDelete); } EmitDeleteAndGoToEnd(OD); diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index ebbc0addfed2c..30127460eeb9b 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -1348,9 +1348,11 @@ static void EmitNewInitializer(CodeGenFunction &CGF, const CXXNewExpr *E, static RValue EmitNewDeleteCall(CodeGenFunction &CGF, const FunctionDecl *CalleeDecl, const FunctionProtoType *CalleeType, - const CallArgList &Args) { + const CallArgList &Args, + llvm::Constant *CalleeOverride = nullptr) { llvm::CallBase *CallOrInvoke; - llvm::Constant *CalleePtr = CGF.CGM.GetAddrOfFunction(CalleeDecl); + llvm::Constant *CalleePtr = + CalleeOverride ? CalleeOverride : CGF.CGM.GetAddrOfFunction(CalleeDecl); CGCallee Callee = CGCallee::forDirect(CalleePtr, GlobalDecl(CalleeDecl)); RValue RV = CGF.EmitCall(CGF.CGM.getTypes().arrangeFreeFunctionCall( Args, CalleeType, /*ChainCall=*/false), @@ -1811,7 +1813,8 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD, llvm::Value *DeletePtr, QualType DeleteTy, llvm::Value *NumElements, - CharUnits CookieSize) { + CharUnits CookieSize, + llvm::Constant *CalleeOverride) { assert((!NumElements && CookieSize.isZero()) || DeleteFD->getOverloadedOperator() == OO_Array_Delete); @@ -1879,7 +1882,7 @@ void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD, "unknown parameter to usual delete function"); // Emit the call to delete. - EmitNewDeleteCall(*this, DeleteFD, DeleteFTy, DeleteArgs); + EmitNewDeleteCall(*this, DeleteFD, DeleteFTy, DeleteArgs, CalleeOverride); // If call argument lowering didn't use a generated tag argument alloca we // remove them @@ -2092,6 +2095,23 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) { const Expr *Arg = E->getArgument(); Address Ptr = EmitPointerWithAlignment(Arg); + // If this is a ::delete expression (explicit global scope) on a class type + // with a non-trivial destructor, note it so we emit __global_delete + // forwarding bodies. This matches MSVC which only engages the __global_delete + // machinery when a deleting destructor is involved: + // - a plain `delete`/`delete[]` (no `::`) never triggers it, even when it + // resolves to a global operator delete; + // - `::delete` on a non-class type (e.g. `::delete intPtr`) or on a class + // with a trivial destructor is lowered as a plain direct operator delete + // and does not trigger it; + // - the destructor's virtualness and the presence of a class-level + // operator delete are both irrelevant to the trigger. + if (E->isGlobalDelete() && CGM.getTarget().getCXXABI().isMicrosoft()) { + const CXXRecordDecl *RD = E->getDestroyedType()->getAsCXXRecordDecl(); + if (RD && RD->hasDefinition() && !RD->hasTrivialDestructor()) + CGM.noteDirectGlobalDelete(); + } + // Null check the pointer. // // We could avoid this null check if we can determine that the object diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 190c177da5935..f2b915ad170b7 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3306,7 +3306,8 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitDeleteCall(const FunctionDecl *DeleteFD, llvm::Value *Ptr, QualType DeleteTy, llvm::Value *NumElements = nullptr, - CharUnits CookieSize = CharUnits()); + CharUnits CookieSize = CharUnits(), + llvm::Constant *CalleeOverride = nullptr); RValue EmitBuiltinNewDeleteCall(const FunctionProtoType *Type, const CallExpr *TheCallExpr, bool IsDelete); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 3fedeabb56480..012a1115ca7d3 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -1140,6 +1140,7 @@ void CodeGenModule::Release() { applyReplacements(); emitMultiVersionFunctions(); emitPFPFieldsWithEvaluatedOffset(); + emitGlobalDeleteForwardingBodies(); if (Context.getLangOpts().IncrementalExtensions && GlobalTopLevelStmtBlockInFlight.first) { @@ -8910,3 +8911,54 @@ void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) { // even if destructor is only declared. addDeferredDeclToEmit(VectorDtorGD); } + +void CodeGenModule::addPendingGlobalDelete( + llvm::Function *GlobalDeleteFn, const FunctionDecl *OperatorDeleteFD) { + // insert() is a no-op if this wrapper has already been recorded, keeping the + // first FunctionDecl seen for it. + PendingMSVCGlobalDeletes.insert({GlobalDeleteFn, OperatorDeleteFD}); +} + +void CodeGenModule::noteDirectGlobalDelete() { HasDirectGlobalDelete = true; } + +void CodeGenModule::emitGlobalDeleteForwardingBodies() { + // MSVC-compatible __global_delete forwarding bodies. + // + // Destructor helpers call __global_delete but they are only needed if there + // is a direct use of ::operator delete. When this TU contains a ::delete + // expression (or a dllexport deleting destructor that takes the global-delete + // path), we know ::operator delete must exist, so we emit a real + // __global_delete definition that forwards to it. + if (!HasDirectGlobalDelete) + return; + + for (const auto &Entry : PendingMSVCGlobalDeletes) { + llvm::Function *GlobDelFn = Entry.first; + if (!GlobDelFn->isDeclaration()) + continue; + + const FunctionDecl *OperatorDeleteFD = Entry.second; + llvm::Constant *RealDeleteFn = GetAddrOfFunction(OperatorDeleteFD); + + // Create the forwarding body: call ::operator delete with all args. + auto *BB = + llvm::BasicBlock::Create(getModule().getContext(), "", GlobDelFn); + llvm::SmallVector Args; + for (auto &Arg : GlobDelFn->args()) + Args.push_back(&Arg); + llvm::CallInst::Create(GlobDelFn->getFunctionType(), RealDeleteFn, Args, "", + BB); + llvm::ReturnInst::Create(getModule().getContext(), BB); + + // Use LinkOnceODR so multiple TUs can emit this without conflicts. + GlobDelFn->setLinkage(llvm::GlobalValue::LinkOnceODRLinkage); + GlobDelFn->setComdat(getModule().getOrInsertComdat(GlobDelFn->getName())); + SetLLVMFunctionAttributes( + GlobalDecl(OperatorDeleteFD), + getTypes().arrangeGlobalDeclaration(GlobalDecl(OperatorDeleteFD)), + GlobDelFn, /*IsThunk=*/false); + SetLLVMFunctionAttributesForDefinition(OperatorDeleteFD, GlobDelFn); + getTargetCodeGenInfo().setTargetAttributes(OperatorDeleteFD, GlobDelFn, + *this); + } +} diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 0cafb041bd957..0abd75ccb0551 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -561,6 +561,16 @@ class CodeGenModule : public CodeGenTypeCache { /// was emitted for the class. llvm::SmallPtrSet RequireVectorDeletingDtor; + /// Pending MSVC __global_delete variants that may need forwarding bodies. + /// Maps each __global_delete wrapper function to the corresponding global + /// ::operator delete FunctionDecl, in insertion order. + llvm::MapVector + PendingMSVCGlobalDeletes; + + /// Whether this TU contains a direct use of global ::operator delete + /// (indicating that __global_delete forwarding bodies should be emitted). + bool HasDirectGlobalDelete = false; + typedef std::pair GlobalInitData; @@ -1643,6 +1653,17 @@ class CodeGenModule : public CodeGenTypeCache { /// destructor definition in a form of alias to the actual definition. void requireVectorDestructorDefinition(const CXXRecordDecl *RD); + /// Record a pending __global_delete variant that may need a forwarding body. + void addPendingGlobalDelete(llvm::Function *GlobalDeleteFn, + const FunctionDecl *OperatorDeleteFD); + + /// Note that global ::operator delete is directly used in this TU. + void noteDirectGlobalDelete(); + + /// Emit __global_delete forwarding bodies for any pending variants, + /// if this TU directly uses global ::operator delete. + void emitGlobalDeleteForwardingBodies(); + /// Check that class need vector deleting destructor body. bool classNeedsVectorDestructor(const CXXRecordDecl *RD); diff --git a/clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp b/clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp index c83cb32251462..97ed0c57e8f68 100644 --- a/clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp +++ b/clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp @@ -229,8 +229,8 @@ H::~H() { call_in_dtor(); } // CLANG22-MSABI-NEXT: br i1 %[[CHCK2]], label %dtor.call_class_delete, label %dtor.call_glob_delete // // CLANG22-MSABI-LABEL: dtor.call_glob_delete: -// CLANG22-MSABI64: call void @"??3@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef 48) -// CLANG22-MSABI32: call void @"??3@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 32, i32 noundef 16) +// CLANG22-MSABI64: call void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef 48) +// CLANG22-MSABI32: call void @"?__global_delete@@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 32, i32 noundef 16) // CLANG22-MSABI-NEXT: br label %[[RETURN:.*]] // // CLANG21-MSABI: dtor.call_delete: @@ -284,8 +284,8 @@ I::~I() { call_in_dtor(); } // CLANG22-MSABI-NEXT: br i1 %[[CHCK2]], label %dtor.call_class_delete, label %dtor.call_glob_delete // // CLANG22-MSABI: dtor.call_glob_delete: -// CLANG22-MSABI64: call void @"??3@YAXPEAX_KW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i64 noundef 96, i64 noundef 32) -// CLANG22-MSABI32: call void @"??3@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 64, i32 noundef 32) +// CLANG22-MSABI64: call void @"?__global_delete@@YAXPEAX_KW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i64 noundef 96, i64 noundef 32) +// CLANG22-MSABI32: call void @"?__global_delete@@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 64, i32 noundef 32) // CLANG22-MSABI-NEXT: br label %[[RETURN:.*]] // // CLANG21-MSABI: dtor.call_delete: diff --git a/clang/test/CodeGenCXX/microsoft-abi-structors.cpp b/clang/test/CodeGenCXX/microsoft-abi-structors.cpp index 670988fc1ada2..1a4a291e28c0a 100644 --- a/clang/test/CodeGenCXX/microsoft-abi-structors.cpp +++ b/clang/test/CodeGenCXX/microsoft-abi-structors.cpp @@ -487,7 +487,7 @@ void checkH() { // DTORS-NEXT: br i1 %[[CONDITION1]], label %[[CALL_CLASS_DELETE:[0-9a-z._]+]], label %[[CALL_GLOB_DELETE:[0-9a-z._]+]] // // DTORS: [[CALL_GLOB_DELETE]] -// DTORS-NEXT: call void @"??3@YAXPAX@Z"(ptr %[[THIS]]) +// DTORS-NEXT: call void @"?__global_delete@@YAXPAX@Z"(ptr %[[THIS]]) // DTORS-NEXT: br label %[[CONTINUE_LABEL]] // // DTORS: [[CALL_CLASS_DELETE]] diff --git a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp index 63ca417a2a967..da891a138739c 100644 --- a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp +++ b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp @@ -42,6 +42,17 @@ struct AllocatedAsArray : public Bird { }; +struct KernelBase { + static void* operator new(__SIZE_TYPE__ n, int tag = 0); + static void operator delete(void* p); + static void operator delete[](void* p); + virtual ~KernelBase(); +}; + +struct KernelDerived : KernelBase { + virtual ~KernelDerived(); +}; + // Vector deleting dtor for Bird is an alias because no new Bird[] expressions // in the TU. // X64: @"??_EBird@@UEAAPEAXI@Z" = weak dso_local unnamed_addr alias ptr (ptr, i32), ptr @"??_GBird@@UEAAPEAXI@Z" @@ -83,6 +94,14 @@ void bar() { sp.foo(); } +KernelBase::~KernelBase() {} +KernelDerived::~KernelDerived() {} + +void kernelTest() { + KernelBase *p = new KernelDerived[2]; + delete[] p; +} + // CHECK-LABEL: define dso_local void @{{.*}}dealloc{{.*}}( // CHECK-SAME: ptr noundef %[[PTR:.*]]) // CHECK: entry: @@ -260,10 +279,38 @@ void bar() { // X86-NEXT: %[[ARRSZ:.*]] = mul i32 4, %[[COOKIE:.*]] // X64-NEXT: %[[TOTALSZ:.*]] = add i64 %[[ARRSZ]], 8 // X86-NEXT: %[[TOTALSZ:.*]] = add i32 %[[ARRSZ]], 4 -// X64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]]) -// X86-NEXT: call void @"??_V@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) +// X64-NEXT: call void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]]) +// X86-NEXT: call void @"?__global_array_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) // CHECK-NEXT: br label %dtor.continue +// Test that when a class provides its own operator delete, the deleting +// destructor calls __global_delete instead of directly +// referencing ::operator delete. This is critical for environments like +// kernel mode where no global ::operator delete exists. +// Verify __empty_global_delete traps (the code path is unreachable at runtime). +// X64: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// X64-NEXT: call void @llvm.trap() +// X64-NEXT: unreachable + +// Verify that when ::delete is used in the TU, a real __global_array_delete +// forwarding body is emitted that calls through to the actual ::operator delete[]. +// X64: define linkonce_odr void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// X64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr %0, i64 %1) +// X64-NEXT: ret void + +// X64-LABEL: define weak dso_local noundef ptr @"??_EKernelDerived@@UEAAPEAXI@Z" +// Verify the array delete path in the VDD uses __global_array_delete. +// X64: dtor.call_glob_delete_after_array_destroy: +// X64: call void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef %{{.*}}) +// Verify the scalar deleting dtor uses __global_delete, not ::operator delete. +// X64: dtor.call_delete: +// X64-NEXT: %[[FLAGCHECK:.*]] = and i32 %should_call_delete2, 4 +// X64-NEXT: %[[ISGLOB:.*]] = icmp eq i32 %[[FLAGCHECK]], 0 +// X64-NEXT: br i1 %[[ISGLOB]], label %dtor.call_class_delete, label %dtor.call_glob_delete +// X64: dtor.call_glob_delete: +// X64-NEXT: call void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef 8) +// X64: dtor.call_class_delete: +// X64-NEXT: call void @"??3KernelBase@@SAXPEAX@Z"(ptr noundef %{{.*}}) struct BaseDelete1 { @@ -346,3 +393,8 @@ void foobartest() { // X64: define weak dso_local noundef ptr @"??_EAllocatedAsArray@@UEAAPEAXI@Z" // X86: define weak dso_local x86_thiscallcc noundef ptr @"??_EAllocatedAsArray@@UAEPAXI@Z" // CLANG21: define linkonce_odr dso_local noundef ptr @"??_GAllocatedAsArray@@UEAAPEAXI@Z" + +// Verify the /ALTERNATENAME linker directive. +// X64: !{!"/alternatename:?__global_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} + +// CLANG21-NOT: __global_delete diff --git a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp index c6089bb5ecbba..0cb596b9a5717 100644 --- a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp +++ b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp @@ -59,7 +59,7 @@ void TesttheTest() { // X64: define weak dso_local noundef ptr @"??_EDrawingBuffer@@UEAAPEAXI@Z" // X64: call void @"??1DrawingBuffer@@UEAA@XZ"(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %arraydestroy.element) // X64: call void @"??_V?$RefCounted@UDrawingBuffer@@@@SAXPEAX@Z"(ptr noundef %2) -// X64: call void @"??_V@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %{{.*}}) +// X64: call void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %{{.*}}) // X64: call void @"??1DrawingBuffer@@UEAA@XZ"(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %this1) // X64: call void @"??3@YAXPEAX_K@Z"(ptr noundef %this1, i64 noundef {{.*}}) @@ -70,7 +70,7 @@ void TesttheTest() { // X86: define weak dso_local x86_thiscallcc noundef ptr @"??_EDrawingBuffer@@UAEPAXI@Z" // X86: call x86_thiscallcc void @"??1DrawingBuffer@@UAE@XZ"(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %arraydestroy.element) // X86: call void @"??_V?$RefCounted@UDrawingBuffer@@@@SAXPAX@Z"(ptr noundef %2) -// X86: call void @"??_V@YAXPAXI@Z"(ptr noundef %2, i32 noundef {{.*}}) +// X86: call void @"?__global_array_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef {{.*}}) // X86 call x86_thiscallcc void @"??1DrawingBuffer@@UAE@XZ"(ptr noundef nonnull align 4 dereferenceable(4) %this1) // X86: call void @"??3@YAXPAXI@Z"(ptr noundef %this1, i32 noundef {{.*}}) @@ -94,6 +94,12 @@ void TesttheTest() { // X64: define linkonce_odr dso_local void @"??_V?$RefCounted@UDrawingBuffer@@@@SAXPEAX@Z"(ptr noundef %p) // X86: define linkonce_odr dso_local void @"??_V?$RefCounted@UDrawingBuffer@@@@SAXPAX@Z"(ptr noundef %p) +// Verify that the dllexport class triggers __global_array_delete forwarding +// body emission even without a ::delete expression in the TU. +// X64: define linkonce_odr void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// X64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr %0, i64 %1) +// X64-NEXT: ret void + // X86: define linkonce_odr dso_local x86_thiscallcc noundef ptr @"??_GNoExport@@UAEPAXI@Z"(ptr noundef nonnull align 4 dereferenceable(4) %this, i32 noundef %should_call_delete) // X64: define linkonce_odr dso_local noundef ptr @"??_GNoExport@@UEAAPEAXI@Z"(ptr noundef nonnull align 8 dereferenceable(8) %this, i32 noundef %should_call_delete) // CHECK-NOT: define {{.*}}_V{{.*}}NoExport diff --git a/clang/test/CodeGenCXX/msvc-global-delete-scalar-array-split.cpp b/clang/test/CodeGenCXX/msvc-global-delete-scalar-array-split.cpp new file mode 100644 index 0000000000000..1ff622b0465bb --- /dev/null +++ b/clang/test/CodeGenCXX/msvc-global-delete-scalar-array-split.cpp @@ -0,0 +1,63 @@ +// RUN: %clang_cc1 -emit-llvm -fms-extensions %s -triple=x86_64-pc-windows-msvc -o - | FileCheck %s + +// Scalar `operator delete` and array `operator delete[]` use DISTINCT global +// delete wrappers even though their mangled signatures are otherwise identical +// (both YAXPEAX_K@Z): __global_delete for scalar, __global_array_delete for +// array. Conflating them would forward one path to the wrong global operator +// delete. This matches MSVC (validated against cl.exe 19.44), which likewise +// uses __global_delete vs __global_array_delete and shares one empty fallback. + +using sz = decltype(sizeof(0)); + +// Member scalar operator delete -> its deleting destructor's global path uses +// __global_delete. +struct Scalar { + void *operator new(sz); + void operator delete(void *, sz); + virtual ~Scalar(); +}; +struct ScalarD : Scalar { + virtual ~ScalarD(); +}; +Scalar::~Scalar() {} +ScalarD::~ScalarD() {} + +// Member array operator delete[] -> its vector deleting destructor's global +// path uses __global_array_delete. +struct Array { + void *operator new[](sz); + void operator delete[](void *, sz); + virtual ~Array(); +}; +struct ArrayD : Array { + virtual ~ArrayD(); +}; +Array::~Array() {} +ArrayD::~ArrayD() {} + +// ::delete on class types with non-trivial destructors triggers the forwarding +// bodies for both wrappers. +void test() { + ::delete new ScalarD(); + ArrayD *a = new ArrayD[2]; + ::delete[] a; +} + +// The scalar deleting destructor's global path calls __global_delete. +// CHECK: call void @"?__global_delete@@YAXPEAX_K@Z"( + +// It forwards to the scalar ??3@ (::operator delete). +// CHECK: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// CHECK-NEXT: call void @"??3@YAXPEAX_K@Z"(ptr %0, i64 %1) + +// The vector deleting destructor's global array path calls __global_array_delete. +// CHECK: call void @"?__global_array_delete@@YAXPEAX_K@Z"( + +// It forwards to the array ??_V@ (::operator delete[]). +// CHECK: define linkonce_odr void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// CHECK-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr %0, i64 %1) + +// Both wrappers share a single __empty_global_delete fallback, wired via +// /ALTERNATENAME. +// CHECK-DAG: !{!"/alternatename:?__global_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} +// CHECK-DAG: !{!"/alternatename:?__global_array_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} diff --git a/clang/test/CodeGenCXX/msvc-global-delete-scope-no-dtor.cpp b/clang/test/CodeGenCXX/msvc-global-delete-scope-no-dtor.cpp new file mode 100644 index 0000000000000..df0c2908267f0 --- /dev/null +++ b/clang/test/CodeGenCXX/msvc-global-delete-scope-no-dtor.cpp @@ -0,0 +1,66 @@ +// RUN: %clang_cc1 -emit-llvm -fms-extensions %s -triple=x86_64-pc-windows-msvc -o - \ +// RUN: | FileCheck %s --implicit-check-not='define{{.*}}"?__global_array_delete@@YAXPEAX_K@Z"' + +// A `::delete` that does not run a non-trivial destructor must NOT cause a +// __global_delete forwarding body to be emitted, even when the TU also +// contains a class whose vector deleting destructor routes its global-delete +// path through __global_delete. +// +// This matches MSVC (validated against cl.exe 19.44): MSVC only engages the +// __global_delete machinery when a deleting destructor is involved, i.e. for a +// `::delete` on a class type with a non-trivial destructor. A `::delete` on a +// primitive or on a trivially-destructible class is lowered as a plain direct +// operator delete, so MSVC leaves __global_delete as an unresolved weak +// external that falls back to the trapping __empty_global_delete via +// /ALTERNATENAME. (The destructor's virtualness and whether the class has its +// own operator delete are irrelevant to this trigger.) Emitting a forwarding +// body here would add a hard reference to the global operator delete and could +// reintroduce LNK2001 in environments without one. + +struct Base { + void *operator new[](__SIZE_TYPE__); + void operator delete[](void *); + virtual ~Base(); +}; +struct Derived : Base { + virtual ~Derived(); +}; +Base::~Base() {} +Derived::~Derived() {} + +// Forces emission of Derived's vector deleting destructor, which creates the +// __global_delete wrapper (as a declaration) for its global-delete path. +void makeVDD() { + Base *p = new Derived[2]; + delete[] p; +} + +// Explicit global-scope delete on a primitive: no destructor involved, so it +// must not trigger forwarding-body emission. +void scopePrimitive(int *q) { + ::delete q; +} + +// Explicit global-scope delete on a trivially-destructible class: still no +// (non-trivial) destructor to run, so it must not trigger it either. +struct Trivial { + int x; +}; +void scopeTrivial(Trivial *q) { + ::delete q; +} + +// The vector deleting destructor still routes its global array-delete path +// through the __global_array_delete wrapper. +// CHECK: call void @"?__global_array_delete@@YAXPEAX_K@Z" + +// __empty_global_delete is emitted as the trapping fallback (shared by the +// scalar and array wrappers of this signature). +// CHECK: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// CHECK-NEXT: call void @llvm.trap() +// CHECK-NEXT: unreachable + +// The /ALTERNATENAME directive wires __global_array_delete to the trapping +// fallback; no forwarding body of its own is emitted (asserted via the +// --implicit-check-not on the RUN line). +// CHECK: !{!"/alternatename:?__global_array_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} diff --git a/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp b/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp new file mode 100644 index 0000000000000..a4ac2610926cd --- /dev/null +++ b/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -emit-llvm -fms-extensions %s -triple=x86_64-pc-windows-msvc -o - | FileCheck %s + +// Verify that a plain delete (no `::`) does NOT trigger __global_delete +// forwarding body emission, but the VDD still uses the __global_delete wrapper. +// This matches MSVC (validated against cl.exe): a plain delete that resolves to +// a global operator delete never emits a forwarding body; only an explicit +// `::delete` on a class type does. + +struct Base { + void* operator new(__SIZE_TYPE__); + void operator delete(void*); + void operator delete[](void*); + virtual ~Base(); +}; +struct Derived : Base { + virtual ~Derived(); +}; +Base::~Base() {} +Derived::~Derived() {} + +// new[] forces VDD emission; regular delete[], not ::delete[]. +void test() { + Base *p = new Derived[2]; + delete[] p; +} + +// The VDD dispatches between class and global delete: the array path uses the +// __global_array_delete wrapper, the scalar path uses __global_delete. +// CHECK-LABEL: define weak dso_local noundef ptr @"??_EDerived@@UEAAPEAXI@Z" +// CHECK: dtor.call_glob_delete_after_array_destroy: +// CHECK: call void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef %{{.*}}) +// CHECK: dtor.call_glob_delete: +// CHECK-NEXT: call void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef 8) +// CHECK: dtor.call_class_delete: +// CHECK-NEXT: call void @"??3Base@@SAXPEAX@Z"(ptr noundef %{{.*}}) + +// __empty_global_delete should be emitted with a trap. +// CHECK: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// CHECK-NEXT: call void @llvm.trap() +// CHECK-NEXT: unreachable + +// Neither wrapper should have a forwarding body (no `::delete` on a class +// type in this TU, and no dllexport class). +// CHECK-NOT: define {{.*}}void @"?__global_delete@@YAXPEAX_K@Z" +// CHECK-NOT: define {{.*}}void @"?__global_array_delete@@YAXPEAX_K@Z" + +// Verify the /ALTERNATENAME linker directive. +// CHECK: !{!"/alternatename:?__global_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} diff --git a/clang/test/CodeGenCXX/msvc-vector-deleting-dtors-sized-delete.cpp b/clang/test/CodeGenCXX/msvc-vector-deleting-dtors-sized-delete.cpp index 6c9faa88e08e9..55ca315a8fb4b 100644 --- a/clang/test/CodeGenCXX/msvc-vector-deleting-dtors-sized-delete.cpp +++ b/clang/test/CodeGenCXX/msvc-vector-deleting-dtors-sized-delete.cpp @@ -50,5 +50,5 @@ void test() { // X86-NEXT: %[[ARRSZ1:.*]] = mul i32 12, %[[HOWMANY]] // X64-NEXT: %[[TOTALSZ1:.*]] = add i64 %[[ARRSZ1]], 8 // X86-NEXT: %[[TOTALSZ1:.*]] = add i32 %[[ARRSZ1]], 4 -// X64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ1]]) -// X86-NEXT: call void @"??_V@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ1]]) +// X64-NEXT: call void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ1]]) +// X86-NEXT: call void @"?__global_array_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ1]]) diff --git a/clang/test/Modules/glob-delete-with-virtual-dtor.cpp b/clang/test/Modules/glob-delete-with-virtual-dtor.cpp index fb2e2a4decf60..18e90aaca78f0 100644 --- a/clang/test/Modules/glob-delete-with-virtual-dtor.cpp +++ b/clang/test/Modules/glob-delete-with-virtual-dtor.cpp @@ -30,8 +30,8 @@ void out_of_module_tests() { // CHECK-NEXT: br i1 %[[CONDITION1]], label %[[CALL_CLASS_DELETE:[0-9a-z._]+]], label %[[CALL_GLOB_DELETE:[0-9a-z._]+]] // // CHECK: [[CALL_GLOB_DELETE]] -// CHECK32-NEXT: call void @"??3@YAXPAXI@Z" -// CHECK64-NEXT: call void @"??3@YAXPEAX_K@Z" +// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI@Z" +// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K@Z" // CHECK-NEXT: br label %[[CONTINUE_LABEL]] // // CHECK: [[CALL_CLASS_DELETE]] diff --git a/clang/test/Modules/msvc-vector-deleting-destructors.cpp b/clang/test/Modules/msvc-vector-deleting-destructors.cpp index 68faa687251d7..d6ccaf866db88 100644 --- a/clang/test/Modules/msvc-vector-deleting-destructors.cpp +++ b/clang/test/Modules/msvc-vector-deleting-destructors.cpp @@ -24,11 +24,11 @@ void out_of_module_tests(Derived *p, Derived *p1) { // CHECK32-NEXT: %[[ARRSZ:.*]] = mul i32 8, %[[COOKIE:.*]] // CHECK64-NEXT: %[[TOTALSZ:.*]] = add i64 %[[ARRSZ]], 8 // CHECK32-NEXT: %[[TOTALSZ:.*]] = add i32 %[[ARRSZ]], 4 -// CHECK32-NEXT: call void @"??_V@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) -// CHECK64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]]) +// CHECK32-NEXT: call void @"?__global_array_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) +// CHECK64-NEXT: call void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]]) // CHECK: dtor.call_glob_delete: -// CHECK32-NEXT: call void @"??3@YAXPAXI@Z"(ptr noundef %this1, i32 noundef 8) -// CHECK64-NEXT: call void @"??3@YAXPEAX_K@Z"(ptr noundef %this1, i64 noundef 16) +// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI@Z"(ptr noundef %this1, i32 noundef 8) +// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %this1, i64 noundef 16) // CHECK: dtor.call_class_delete: // CHECK32-NEXT: call void @"??3Base2@@SAXPAX@Z"(ptr noundef %this1) // CHECK64-NEXT: call void @"??3Base2@@SAXPEAX@Z"(ptr noundef %this1) diff --git a/clang/test/PCH/glob-delete-with-virtual-dtor.cpp b/clang/test/PCH/glob-delete-with-virtual-dtor.cpp index 29242b04c4a7f..a17b7570bbd65 100644 --- a/clang/test/PCH/glob-delete-with-virtual-dtor.cpp +++ b/clang/test/PCH/glob-delete-with-virtual-dtor.cpp @@ -33,8 +33,8 @@ void out_of_pch_tests() { // CHECK-NEXT: br i1 %[[CONDITION1]], label %[[CALL_CLASS_DELETE:[0-9a-z._]+]], label %[[CALL_GLOB_DELETE:[0-9a-z._]+]] // // CHECK: [[CALL_GLOB_DELETE]] -// CHECK32-NEXT: call void @"??3@YAXPAXI@Z" -// CHECK64-NEXT: call void @"??3@YAXPEAX_K@Z" +// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI@Z" +// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K@Z" // CHECK-NEXT: br label %[[CONTINUE_LABEL]] // // CHECK: [[CALL_CLASS_DELETE]] diff --git a/clang/test/PCH/msvc-vector-deleting-destructors.cpp b/clang/test/PCH/msvc-vector-deleting-destructors.cpp index 1409b41d2df82..845fe4b246892 100644 --- a/clang/test/PCH/msvc-vector-deleting-destructors.cpp +++ b/clang/test/PCH/msvc-vector-deleting-destructors.cpp @@ -28,11 +28,11 @@ void out_of_module_tests(Derived *p, Derived *p1) { // CHECK32-NEXT: %[[ARRSZ:.*]] = mul i32 8, %[[COOKIE:.*]] // CHECK64-NEXT: %[[TOTALSZ:.*]] = add i64 %[[ARRSZ]], 8 // CHECK32-NEXT: %[[TOTALSZ:.*]] = add i32 %[[ARRSZ]], 4 -// CHECK32-NEXT: call void @"??_V@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) -// CHECK64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]]) +// CHECK32-NEXT: call void @"?__global_array_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) +// CHECK64-NEXT: call void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]]) // CHECK: dtor.call_glob_delete: -// CHECK32-NEXT: call void @"??3@YAXPAXI@Z"(ptr noundef %this1, i32 noundef 8) -// CHECK64-NEXT: call void @"??3@YAXPEAX_K@Z"(ptr noundef %this1, i64 noundef 16) +// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI@Z"(ptr noundef %this1, i32 noundef 8) +// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %this1, i64 noundef 16) // CHECK: dtor.call_class_delete: // CHECK32-NEXT: call void @"??3Base2@@SAXPAX@Z"(ptr noundef %this1) // CHECK64-NEXT: call void @"??3Base2@@SAXPEAX@Z"(ptr noundef %this1)