https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/188372
>From b2575be21a92d940f97829666017ce2908186298 Mon Sep 17 00:00:00 2001 From: Daniel Paoliello <[email protected]> Date: Tue, 7 Jul 2026 15:04:35 -0700 Subject: [PATCH 1/3] [clang][win] MSVC-compat: Use __global_delete wrapper in deleting destructors instead of directly referencing ::operator delete When Clang emits scalar/vector deleting destructors for classes with a class-level `operator delete`, it generates a conditional dispatch that can call either the class-level or global `::operator delete`. The global path directly referenced `::operator delete`, causing `LNK2001` linker errors in environments where no global `::operator delete` exists. MSVC handles this by calling `__global_delete` - this is a compiler generated function that is ONLY created if there is a direct call to global `::operator delete`. Additionally, it always emits an empty `__empty_global_delete` and uses `/ALTERNATIVENAME` linker arg to default `__global_delete` to `__empty_global_delete` if there is NEVER an actual all to `::operator delete` (thus the empty function should never be called). This change aligns Clang's behavior with MSVC when MSVC compatibility mode and non-LLVM 21 ABI is used, with one difference: the LLVM generated `__empty_global_delete` traps since it should never be called. --- clang/docs/ReleaseNotes.md | 10 ++ clang/lib/CodeGen/CGClass.cpp | 112 +++++++++++++++++- clang/lib/CodeGen/CGExprCXX.cpp | 17 ++- clang/lib/CodeGen/CodeGenFunction.h | 3 +- clang/lib/CodeGen/CodeGenModule.cpp | 46 +++++++ clang/lib/CodeGen/CodeGenModule.h | 20 ++++ .../CodeGenCXX/cxx2a-destroying-delete.cpp | 8 +- .../CodeGenCXX/microsoft-abi-structors.cpp | 2 +- .../microsoft-vector-deleting-dtors.cpp | 56 ++++++++- .../microsoft-vector-deleting-dtors2.cpp | 10 +- .../msvc-no-global-delete-forwarding.cpp | 44 +++++++ ...svc-vector-deleting-dtors-sized-delete.cpp | 4 +- .../Modules/glob-delete-with-virtual-dtor.cpp | 4 +- .../msvc-vector-deleting-destructors.cpp | 8 +- .../PCH/glob-delete-with-virtual-dtor.cpp | 4 +- .../PCH/msvc-vector-deleting-destructors.cpp | 8 +- 16 files changed, 324 insertions(+), 32 deletions(-) create mode 100644 clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index e12ae0b2eeed4..115ad79e86fa4 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -997,6 +997,16 @@ 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`` 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..c094ea9534fbd 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 <optional> @@ -1409,6 +1410,88 @@ 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. +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<llvm::Function>(GlobDeleteCallee); + llvm::FunctionType *FnTy = GlobDeleteFn->getFunctionType(); + + // Derive __global_delete and __empty_global_delete mangled names. + // Global ::operator delete mangling: ??3@<signature> + // Global ::operator delete[] mangling: ??_V@<signature> + // We construct: + // ?__global_delete@@<signature> + // ?__empty_global_delete@@<signature> + StringRef GlobDeleteMangledName = GlobDeleteFn->getName(); + StringRef Signature; + if (GlobDeleteMangledName.starts_with("??3@")) + Signature = GlobDeleteMangledName.substr(4); + else if (GlobDeleteMangledName.starts_with("??_V@")) + Signature = GlobDeleteMangledName.substr(5); + else + llvm_unreachable("unexpected global operator delete mangling"); + + std::string GlobalDeleteName = ("?__global_delete@@" + 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 __empty_global_delete fallback (trap - this path is unreachable + // at runtime when ::delete is never used; see the doc comment above). + llvm::Function *EmptyFn = llvm::Function::Create( + FnTy, llvm::GlobalValue::LinkOnceODRLinkage, EmptyGlobalDeleteName, &M); + EmptyFn->setComdat(M.getOrInsertComdat(EmptyGlobalDeleteName)); + EmptyFn->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + CGM.SetLLVMFunctionAttributesForDefinition(GlobOD, EmptyFn); + 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); + + // Emit /ALTERNATENAME linker directive: if __global_delete 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); + + // Nothing directly uses this function other than the /alternatename + // directive, so explicitly mark it as used. + appendToUsed(M, {EmptyFn}); + + // Return the __global_delete wrapper function to call. + auto GlobalDeleteCallee = M.getOrInsertFunction(GlobalDeleteName, FnTy); + + // 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(GlobalDeleteName, GlobOD); + + return cast<llvm::Function>(GlobalDeleteCallee.getCallee()); +} + static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, CodeGenFunction &CGF, llvm::Value *ShouldDeleteCondition) { @@ -1492,9 +1575,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<DLLExportAttr>()) + 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 +1813,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 +1842,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<DLLExportAttr>()) + 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..7da4315eb782f 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,12 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) { const Expr *Arg = E->getArgument(); Address Ptr = EmitPointerWithAlignment(Arg); + // If this is a ::delete expression (explicit global scope), note it so we + // emit __global_delete forwarding bodies. Only ::delete triggers this, not + // regular delete expressions that happen to resolve to a global operator. + if (E->isGlobalDelete() && CGM.getTarget().getCXXABI().isMicrosoft()) + 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 6d0718c243812..b6e66c7ff456d 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3301,7 +3301,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 e155fdd752d7f..5c1c59aa12340 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -1085,6 +1085,7 @@ void CodeGenModule::Release() { applyReplacements(); emitMultiVersionFunctions(); emitPFPFieldsWithEvaluatedOffset(); + emitGlobalDeleteForwardingBodies(); if (Context.getLangOpts().IncrementalExtensions && GlobalTopLevelStmtBlockInFlight.first) { @@ -8855,3 +8856,48 @@ void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) { // even if destructor is only declared. addDeferredDeclToEmit(VectorDtorGD); } + +void CodeGenModule::addPendingGlobalDelete( + StringRef GlobalDeleteName, const FunctionDecl *OperatorDeleteFD) { + // insert() is a no-op if this name has already been recorded, keeping the + // first FunctionDecl seen for it. + PendingMSVCGlobalDeletes.insert({GlobalDeleteName.str(), 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 = getModule().getFunction(Entry.first); + if (!GlobDelFn || !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<llvm::Value *, 4> 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())); + SetLLVMFunctionAttributesForDefinition(OperatorDeleteFD, GlobDelFn); + } +} diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index b1e70a7c347db..4638aa28bd02b 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -561,6 +561,15 @@ class CodeGenModule : public CodeGenTypeCache { /// was emitted for the class. llvm::SmallPtrSet<const CXXRecordDecl *, 16> RequireVectorDeletingDtor; + /// Pending MSVC __global_delete variants that may need forwarding bodies. + /// Maps the __global_delete mangled name to the corresponding global + /// ::operator delete FunctionDecl, in insertion order. + llvm::MapVector<std::string, const FunctionDecl *> 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<OrderGlobalInitsOrStermFinalizers, llvm::Function *> GlobalInitData; @@ -1637,6 +1646,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(StringRef GlobalDeleteName, + 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..8a700809d95ad 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_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]]) +// X86-NEXT: call void @"?__global_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 (a weak external) 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 %0, i64 %1) +// X64-NEXT: call void @llvm.trap() +// X64-NEXT: unreachable + +// Verify that when ::delete is used in the TU, a real __global_delete +// forwarding body is emitted that calls through to the actual ::operator delete. +// X64: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr %0, i64 %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_delete. +// X64: dtor.call_glob_delete_after_array_destroy: +// X64: call void @"?__global_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..7483731d31d46 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_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_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_delete forwarding body +// emission even without a ::delete expression in the TU. +// X64: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr %0, i64 %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-no-global-delete-forwarding.cpp b/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp new file mode 100644 index 0000000000000..8c75ca18cb9b7 --- /dev/null +++ b/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -emit-llvm -fms-extensions %s -triple=x86_64-pc-windows-msvc -o - | FileCheck %s + +// Verify that regular delete (not ::delete) does NOT trigger __global_delete +// forwarding body emission, but the VDD still uses __global_delete wrapper. +// This matches MSVC behavior where only ::delete triggers forwarding bodies. + +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 using __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_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 %0, i64 %1) +// CHECK-NEXT: call void @llvm.trap() +// CHECK-NEXT: unreachable + +// __global_delete should NOT have a forwarding body (no ::delete in this TU, +// no dllexport class). +// CHECK-NOT: define {{.*}}void @"?__global_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..db8c429956b6f 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_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ1]]) +// X86-NEXT: call void @"?__global_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..9e99ae1e191b7 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_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) +// CHECK64-NEXT: call void @"?__global_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..b17fe35240c89 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_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) +// CHECK64-NEXT: call void @"?__global_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) >From dc3e5fa9c504901b86a5e76d9e6fe2628ae37aee Mon Sep 17 00:00:00 2001 From: Daniel Paoliello <[email protected]> Date: Tue, 7 Jul 2026 18:24:30 -0700 Subject: [PATCH 2/3] Add comment to clarify --- clang/lib/CodeGen/CGExprCXX.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 7da4315eb782f..4c19f61450d34 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -2098,6 +2098,10 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) { // If this is a ::delete expression (explicit global scope), note it so we // emit __global_delete forwarding bodies. Only ::delete triggers this, not // regular delete expressions that happen to resolve to a global operator. + // This matches MSVC: a plain `delete`/`delete[]` that resolves to a global + // operator delete does NOT cause MSVC to emit a __global_delete body (even + // when it uses the same signature the vector deleting destructor routes + // through); only an explicit `::delete` does. if (E->isGlobalDelete() && CGM.getTarget().getCXXABI().isMicrosoft()) CGM.noteDirectGlobalDelete(); >From b6c2e5f1ea24adbdd61519b4ea4b51472b801257 Mon Sep 17 00:00:00 2001 From: Daniel Paoliello <[email protected]> Date: Wed, 8 Jul 2026 11:48:23 -0700 Subject: [PATCH 3/3] Handle vector deletes correctly, use a map of functions instead of names, only emit __global_delete body for calls to global op with a class type with a non-trivial dtor --- clang/docs/ReleaseNotes.md | 3 +- clang/lib/CodeGen/CGClass.cpp | 85 ++++++++++++------- clang/lib/CodeGen/CGExprCXX.cpp | 25 ++++-- clang/lib/CodeGen/CodeGenModule.cpp | 10 +-- clang/lib/CodeGen/CodeGenModule.h | 7 +- .../microsoft-vector-deleting-dtors.cpp | 14 +-- .../microsoft-vector-deleting-dtors2.cpp | 10 +-- .../msvc-no-global-delete-forwarding.cpp | 18 ++-- ...svc-vector-deleting-dtors-sized-delete.cpp | 4 +- .../msvc-vector-deleting-destructors.cpp | 4 +- .../PCH/msvc-vector-deleting-destructors.cpp | 4 +- 11 files changed, 108 insertions(+), 76 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 115ad79e86fa4..5ab9994d0302b 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -998,7 +998,8 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the explicit unwind version was specified. - In MSVC compatibility mode, scalar and vector deleting destructors now call - ``__global_delete`` instead of directly referencing ``::operator delete``. + ``__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`` diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index c094ea9534fbd..ce242f28dc8cf 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -1419,7 +1419,12 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF, /// 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. +/// 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) { @@ -1432,22 +1437,28 @@ getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, auto *GlobDeleteFn = cast<llvm::Function>(GlobDeleteCallee); llvm::FunctionType *FnTy = GlobDeleteFn->getFunctionType(); - // Derive __global_delete and __empty_global_delete mangled names. - // Global ::operator delete mangling: ??3@<signature> - // Global ::operator delete[] mangling: ??_V@<signature> - // We construct: - // ?__global_delete@@<signature> - // ?__empty_global_delete@@<signature> + // 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@<signature> + // -> wrapper ?__global_delete@@<signature> + // Global ::operator delete[] mangling: ??_V@<signature> + // -> wrapper ?__global_array_delete@@<signature> + // shared fallback: ?__empty_global_delete@@<signature> StringRef GlobDeleteMangledName = GlobDeleteFn->getName(); StringRef Signature; - if (GlobDeleteMangledName.starts_with("??3@")) + const char *WrapperBase; + if (GlobDeleteMangledName.starts_with("??3@")) { Signature = GlobDeleteMangledName.substr(4); - else if (GlobDeleteMangledName.starts_with("??_V@")) + WrapperBase = "?__global_delete@@"; + } else if (GlobDeleteMangledName.starts_with("??_V@")) { Signature = GlobDeleteMangledName.substr(5); - else + WrapperBase = "?__global_array_delete@@"; + } else { llvm_unreachable("unexpected global operator delete mangling"); + } - std::string GlobalDeleteName = ("?__global_delete@@" + Signature).str(); + std::string GlobalDeleteName = (WrapperBase + Signature).str(); std::string EmptyGlobalDeleteName = ("?__empty_global_delete@@" + Signature).str(); @@ -1455,22 +1466,33 @@ getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, if (llvm::Function *Existing = M.getFunction(GlobalDeleteName)) return Existing; - // Create __empty_global_delete fallback (trap - this path is unreachable - // at runtime when ::delete is never used; see the doc comment above). - llvm::Function *EmptyFn = llvm::Function::Create( - FnTy, llvm::GlobalValue::LinkOnceODRLinkage, EmptyGlobalDeleteName, &M); - EmptyFn->setComdat(M.getOrInsertComdat(EmptyGlobalDeleteName)); - EmptyFn->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); - CGM.SetLLVMFunctionAttributesForDefinition(GlobOD, EmptyFn); - 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); - - // Emit /ALTERNATENAME linker directive: if __global_delete isn't provided, + // 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.SetLLVMFunctionAttributesForDefinition(GlobOD, EmptyFn); + 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; @@ -1478,18 +1500,15 @@ getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, llvm::MDNode::get(LLVMCtx, {llvm::MDString::get(LLVMCtx, AltOption)}); M.getOrInsertNamedMetadata("llvm.linker.options")->addOperand(AltMD); - // Nothing directly uses this function other than the /alternatename - // directive, so explicitly mark it as used. - appendToUsed(M, {EmptyFn}); - // Return the __global_delete wrapper function to call. auto GlobalDeleteCallee = M.getOrInsertFunction(GlobalDeleteName, FnTy); + auto *GlobalDeleteFn = cast<llvm::Function>(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(GlobalDeleteName, GlobOD); + CGM.addPendingGlobalDelete(GlobalDeleteFn, GlobOD); - return cast<llvm::Function>(GlobalDeleteCallee.getCallee()); + return GlobalDeleteFn; } static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 4c19f61450d34..30127460eeb9b 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -2095,15 +2095,22 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) { const Expr *Arg = E->getArgument(); Address Ptr = EmitPointerWithAlignment(Arg); - // If this is a ::delete expression (explicit global scope), note it so we - // emit __global_delete forwarding bodies. Only ::delete triggers this, not - // regular delete expressions that happen to resolve to a global operator. - // This matches MSVC: a plain `delete`/`delete[]` that resolves to a global - // operator delete does NOT cause MSVC to emit a __global_delete body (even - // when it uses the same signature the vector deleting destructor routes - // through); only an explicit `::delete` does. - if (E->isGlobalDelete() && CGM.getTarget().getCXXABI().isMicrosoft()) - CGM.noteDirectGlobalDelete(); + // 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. // diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 5c1c59aa12340..cab010d4fbf07 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -8858,10 +8858,10 @@ void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) { } void CodeGenModule::addPendingGlobalDelete( - StringRef GlobalDeleteName, const FunctionDecl *OperatorDeleteFD) { - // insert() is a no-op if this name has already been recorded, keeping the + 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({GlobalDeleteName.str(), OperatorDeleteFD}); + PendingMSVCGlobalDeletes.insert({GlobalDeleteFn, OperatorDeleteFD}); } void CodeGenModule::noteDirectGlobalDelete() { HasDirectGlobalDelete = true; } @@ -8878,8 +8878,8 @@ void CodeGenModule::emitGlobalDeleteForwardingBodies() { return; for (const auto &Entry : PendingMSVCGlobalDeletes) { - llvm::Function *GlobDelFn = getModule().getFunction(Entry.first); - if (!GlobDelFn || !GlobDelFn->isDeclaration()) + llvm::Function *GlobDelFn = Entry.first; + if (!GlobDelFn->isDeclaration()) continue; const FunctionDecl *OperatorDeleteFD = Entry.second; diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 4638aa28bd02b..812ab34ba413e 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -562,9 +562,10 @@ class CodeGenModule : public CodeGenTypeCache { llvm::SmallPtrSet<const CXXRecordDecl *, 16> RequireVectorDeletingDtor; /// Pending MSVC __global_delete variants that may need forwarding bodies. - /// Maps the __global_delete mangled name to the corresponding global + /// Maps each __global_delete wrapper function to the corresponding global /// ::operator delete FunctionDecl, in insertion order. - llvm::MapVector<std::string, const FunctionDecl *> PendingMSVCGlobalDeletes; + llvm::MapVector<llvm::Function *, const FunctionDecl *> + PendingMSVCGlobalDeletes; /// Whether this TU contains a direct use of global ::operator delete /// (indicating that __global_delete forwarding bodies should be emitted). @@ -1647,7 +1648,7 @@ class CodeGenModule : public CodeGenTypeCache { void requireVectorDestructorDefinition(const CXXRecordDecl *RD); /// Record a pending __global_delete variant that may need a forwarding body. - void addPendingGlobalDelete(StringRef GlobalDeleteName, + void addPendingGlobalDelete(llvm::Function *GlobalDeleteFn, const FunctionDecl *OperatorDeleteFD); /// Note that global ::operator delete is directly used in this TU. diff --git a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp index 8a700809d95ad..1d9f57aeb8f88 100644 --- a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp +++ b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp @@ -279,8 +279,8 @@ void kernelTest() { // 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 @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]]) -// X86-NEXT: call void @"?__global_delete@@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 @@ -292,16 +292,16 @@ void kernelTest() { // X64-NEXT: call void @llvm.trap() // X64-NEXT: unreachable -// Verify that when ::delete is used in the TU, a real __global_delete -// forwarding body is emitted that calls through to the actual ::operator delete. -// X64: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr %0, i64 %1) +// 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 %0, i64 %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_delete. +// Verify the array delete path in the VDD uses __global_array_delete. // X64: dtor.call_glob_delete_after_array_destroy: -// X64: call void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef %{{.*}}) +// 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 diff --git a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp index 7483731d31d46..14936e67ab14c 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 @"?__global_delete@@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 @"?__global_delete@@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,9 +94,9 @@ 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_delete forwarding body -// emission even without a ::delete expression in the TU. -// X64: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr %0, i64 %1) +// 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 %0, i64 %1) // X64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr %0, i64 %1) // X64-NEXT: ret void diff --git a/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp b/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp index 8c75ca18cb9b7..ea5a0c4b373a5 100644 --- a/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp +++ b/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp @@ -1,8 +1,10 @@ // RUN: %clang_cc1 -emit-llvm -fms-extensions %s -triple=x86_64-pc-windows-msvc -o - | FileCheck %s -// Verify that regular delete (not ::delete) does NOT trigger __global_delete -// forwarding body emission, but the VDD still uses __global_delete wrapper. -// This matches MSVC behavior where only ::delete triggers forwarding bodies. +// 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__); @@ -22,10 +24,11 @@ void test() { delete[] p; } -// The VDD dispatches between class and global delete using __global_delete. +// 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_delete@@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef %{{.*}}) +// 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: @@ -36,9 +39,10 @@ void test() { // CHECK-NEXT: call void @llvm.trap() // CHECK-NEXT: unreachable -// __global_delete should NOT have a forwarding body (no ::delete in this TU, -// no dllexport class). +// 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 db8c429956b6f..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 @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ1]]) -// X86-NEXT: call void @"?__global_delete@@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/msvc-vector-deleting-destructors.cpp b/clang/test/Modules/msvc-vector-deleting-destructors.cpp index 9e99ae1e191b7..d6ccaf866db88 100644 --- a/clang/test/Modules/msvc-vector-deleting-destructors.cpp +++ b/clang/test/Modules/msvc-vector-deleting-destructors.cpp @@ -24,8 +24,8 @@ 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 @"?__global_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) -// CHECK64-NEXT: call void @"?__global_delete@@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 @"?__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) diff --git a/clang/test/PCH/msvc-vector-deleting-destructors.cpp b/clang/test/PCH/msvc-vector-deleting-destructors.cpp index b17fe35240c89..845fe4b246892 100644 --- a/clang/test/PCH/msvc-vector-deleting-destructors.cpp +++ b/clang/test/PCH/msvc-vector-deleting-destructors.cpp @@ -28,8 +28,8 @@ 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 @"?__global_delete@@YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]]) -// CHECK64-NEXT: call void @"?__global_delete@@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 @"?__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) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
