https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/188372
>From 42039b3b43fc932d755026f8a452ad934e4882eb Mon Sep 17 00:00:00 2001 From: "Daniel Paoliello (HE/HIM)" <[email protected]> Date: Mon, 23 Mar 2026 12:26:34 -0700 Subject: [PATCH 1/4] [win] MSVC-compat: Use `__global_delete` wrapper in deleting destructors instead of directly referencing `::operator delete` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 (e.g., Windows kernel mode). MSVC handles this by calling `__global_delete` — a weak external that falls back to a no-op `__empty_global_delete` via `/ALTERNATENAME`. This change aligns Clang's behavior with MSVC when MSVC compatibility mode and non-LLVM 21 ABI is used. --- clang/docs/ReleaseNotes.rst | 5 ++ clang/lib/CodeGen/CGClass.cpp | 86 ++++++++++++++++++- clang/lib/CodeGen/CGExprCXX.cpp | 11 ++- clang/lib/CodeGen/CodeGenFunction.h | 3 +- .../CodeGenCXX/cxx2a-destroying-delete.cpp | 8 +- .../CodeGenCXX/microsoft-abi-structors.cpp | 2 +- .../microsoft-vector-deleting-dtors.cpp | 48 ++++++++++- .../microsoft-vector-deleting-dtors2.cpp | 4 +- ...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 +- 13 files changed, 163 insertions(+), 32 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index bbbd3a34e01d6..9a4880ca9525a 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -483,6 +483,11 @@ Windows Support - Clang now defines the ``_MSVC_TRADITIONAL`` macro as ``1`` when emulating MSVC 19.15 (Visual Studio 2017 version 15.8) and later. (#GH47114) +- In MSVC compatibility mode, scalar and vector deleting destructors now call + ``__global_delete`` (a weak external) instead of directly referencing + ``::operator delete``. This matches MSVC's behavior and fixes ``LNK2001`` + linker errors in environments where no global ``::operator delete`` exists. + LoongArch Support ^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 6572009fc6003..25d74816efae7 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> @@ -1435,6 +1436,70 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF, return true; } +/// Get or create the MSVC-compatible __global_delete wrapper function. +/// +/// MSVC's scalar/vector deleting destructors call __global_delete (a weak +/// external) instead of calling ::operator delete directly. This allows +/// environments without a global ::operator delete (e.g., kernel mode) to +/// gracefully fall back to a no-op __empty_global_delete. +static llvm::Constant * +getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, + const FunctionDecl *GlobOD) { + 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. + llvm::Function *EmptyFn = llvm::Function::Create( + FnTy, llvm::GlobalValue::LinkOnceODRLinkage, EmptyGlobalDeleteName, &M); + EmptyFn->setComdat(M.getOrInsertComdat(EmptyGlobalDeleteName)); + EmptyFn->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + auto *BB = llvm::BasicBlock::Create(LLVMCtx, "", EmptyFn); + llvm::ReturnInst::Create(LLVMCtx, BB); + + // Emit /ALTERNATENAME linker directive: if __global_delete isn't provided + // (e.g., by the CRT), fall back to the no-op __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); + return cast<llvm::Function>(GlobalDeleteCallee.getCallee()); +} + static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, CodeGenFunction &CGF, llvm::Value *ShouldDeleteCondition) { @@ -1518,9 +1583,13 @@ static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, CGF.EmitBranchThroughCleanup(CGF.ReturnBlock); CGF.EmitBlock(GlobDelete); + // Use __global_delete wrapper for the global array delete path, + // matching MSVC's weak external mechanism. + llvm::Constant *GlobalDeleteWrapper = getOrCreateMSVCGlobalDeleteWrapper( + CGF.CGM, Dtor->getGlobalArrayOperatorDelete()); CGF.EmitDeleteCall(Dtor->getGlobalArrayOperatorDelete(), allocatedPtr, CGF.getContext().getCanonicalTagType(ClassDecl), - numElements, cookieSize); + numElements, cookieSize, GlobalDeleteWrapper); } } else { // No operators delete[] were found, so emit a trap. @@ -1747,9 +1816,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 @@ -1773,7 +1845,13 @@ 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. This matches MSVC's behavior: __global_delete is a + // weak external that falls back to __empty_global_delete (a no-op) when + // the CRT doesn't provide it (e.g., kernel-mode environments). + llvm::Constant *GlobalDeleteWrapper = + getOrCreateMSVCGlobalDeleteWrapper(CGF.CGM, GlobOD); + EmitDeleteAndGoToEnd(GlobOD, GlobalDeleteWrapper); CGF.EmitBlock(ClassDelete); } EmitDeleteAndGoToEnd(OD); diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 82300c3ede183..630a0159ff907 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -1337,9 +1337,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), @@ -1788,7 +1790,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); @@ -1856,7 +1859,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 diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index fd474c09044ef..8fed623e56718 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3299,7 +3299,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/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 cca1c66806af6..4891ce12f8fed 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,30 @@ 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 with no-op fallback) +// instead of directly referencing ::operator delete. This is critical for +// environments like kernel mode where no global ::operator delete exists. +// Verify __empty_global_delete is emitted as a no-op fallback. +// X64: define linkonce_odr void @"?__empty_global_delete@@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 +385,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 65e2853b82e4d..c725800e2ebcb 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 {{.*}}) 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 675bdc307ee5e3d1fe96cdcb113634322677412d Mon Sep 17 00:00:00 2001 From: "Daniel Paoliello (HE/HIM)" <[email protected]> Date: Wed, 25 Mar 2026 10:24:07 -0700 Subject: [PATCH 2/4] Add assert for MSVC ABI, set attr on __empty_global_delete --- clang/lib/CodeGen/CGClass.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 25d74816efae7..696ae20ccc504 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -1480,6 +1480,7 @@ getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, 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::ReturnInst::Create(LLVMCtx, BB); @@ -1776,6 +1777,8 @@ struct CallDtorDelete final : EHScopeStack::Cleanup { void EmitConditionalDtorDeleteCall(CodeGenFunction &CGF, llvm::Value *ShouldDeleteCondition, bool ReturnAfterDelete) { + assert(CGF.CGM.getTarget().getCXXABI().isMicrosoft() && + "deleting destructor should only be emitted for MSVC ABI"); const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl); const CXXRecordDecl *ClassDecl = Dtor->getParent(); const FunctionDecl *OD = Dtor->getOperatorDelete(); >From 94a7f198fe781ba58b9f638d1b81c83f833efb84 Mon Sep 17 00:00:00 2001 From: "Daniel Paoliello (HE/HIM)" <[email protected]> Date: Wed, 25 Mar 2026 10:26:34 -0700 Subject: [PATCH 3/4] Move assert to correct location --- clang/lib/CodeGen/CGClass.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 696ae20ccc504..6ff4f10fe2c83 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -1445,6 +1445,8 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF, 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(); @@ -1777,8 +1779,6 @@ struct CallDtorDelete final : EHScopeStack::Cleanup { void EmitConditionalDtorDeleteCall(CodeGenFunction &CGF, llvm::Value *ShouldDeleteCondition, bool ReturnAfterDelete) { - assert(CGF.CGM.getTarget().getCXXABI().isMicrosoft() && - "deleting destructor should only be emitted for MSVC ABI"); const CXXDestructorDecl *Dtor = cast<CXXDestructorDecl>(CGF.CurCodeDecl); const CXXRecordDecl *ClassDecl = Dtor->getParent(); const FunctionDecl *OD = Dtor->getOperatorDelete(); >From 9a76b261ce68ce77f8b9f6641d69b5f1365b6dbf Mon Sep 17 00:00:00 2001 From: Daniel Paoliello <[email protected]> Date: Wed, 1 Apr 2026 11:00:46 -0700 Subject: [PATCH 4/4] Generate the __global_delete wrapper when required as well --- clang/docs/ReleaseNotes.rst | 9 ++-- clang/lib/CodeGen/CGClass.cpp | 43 ++++++++++------ clang/lib/CodeGen/CGExprCXX.cpp | 6 +++ clang/lib/CodeGen/CodeGenModule.cpp | 49 +++++++++++++++++++ clang/lib/CodeGen/CodeGenModule.h | 21 ++++++++ .../microsoft-vector-deleting-dtors.cpp | 16 ++++-- 6 files changed, 123 insertions(+), 21 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 9a4880ca9525a..279cbb2f6604b 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -484,9 +484,12 @@ Windows Support 19.15 (Visual Studio 2017 version 15.8) and later. (#GH47114) - In MSVC compatibility mode, scalar and vector deleting destructors now call - ``__global_delete`` (a weak external) instead of directly referencing - ``::operator delete``. This matches MSVC's behavior and fixes ``LNK2001`` - linker errors in environments where no global ``::operator delete`` exists. + ``__global_delete`` instead of directly referencing ``::operator delete``. + This matches MSVC's behavior and fixes ``LNK2001`` linker errors in + environments (such as kernel mode) 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. LoongArch Support ^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 6ff4f10fe2c83..5cdd7949f6de0 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -1438,10 +1438,14 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF, /// Get or create the MSVC-compatible __global_delete wrapper function. /// -/// MSVC's scalar/vector deleting destructors call __global_delete (a weak -/// external) instead of calling ::operator delete directly. This allows -/// environments without a global ::operator delete (e.g., kernel mode) to -/// gracefully fall back to a no-op __empty_global_delete. +/// Destructor helpers call __global_delete instead of ::operator delete +/// directly. __global_delete is a weak symbol that defaults to +/// __empty_global_delete (a trap) via /ALTERNATENAME. When this TU contains +/// a ::delete expression, a real forwarding body is emitted at end-of-file +/// (see CodeGenModule::Release). If ::delete is never used anywhere in the +/// program, the trap default wins - but that path is unreachable at runtime, +/// so it only fires if something is seriously wrong (e.g., memory +/// corruption). Both symbols are compiler-generated. static llvm::Constant * getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, const FunctionDecl *GlobOD) { @@ -1477,17 +1481,23 @@ getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, if (llvm::Function *Existing = M.getFunction(GlobalDeleteName)) return Existing; - // Create __empty_global_delete fallback. + // 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::ReturnInst::Create(LLVMCtx, BB); - - // Emit /ALTERNATENAME linker directive: if __global_delete isn't provided - // (e.g., by the CRT), fall back to the no-op __empty_global_delete. + 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 = @@ -1500,6 +1510,11 @@ getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, // 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()); } @@ -1586,8 +1601,9 @@ static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, CGF.EmitBranchThroughCleanup(CGF.ReturnBlock); CGF.EmitBlock(GlobDelete); - // Use __global_delete wrapper for the global array delete path, - // matching MSVC's weak external mechanism. + // 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()); CGF.EmitDeleteCall(Dtor->getGlobalArrayOperatorDelete(), allocatedPtr, @@ -1849,9 +1865,8 @@ void EmitConditionalDtorDeleteCall(CodeGenFunction &CGF, CGF.EmitBlock(GlobDelete); // Use __global_delete wrapper instead of directly calling - // ::operator delete. This matches MSVC's behavior: __global_delete is a - // weak external that falls back to __empty_global_delete (a no-op) when - // the CRT doesn't provide it (e.g., kernel-mode environments). + // ::operator delete to match MSVC's behavior. See the doc comment on + // getOrCreateMSVCGlobalDeleteWrapper for details. llvm::Constant *GlobalDeleteWrapper = getOrCreateMSVCGlobalDeleteWrapper(CGF.CGM, GlobOD); EmitDeleteAndGoToEnd(GlobOD, GlobalDeleteWrapper); diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 630a0159ff907..5b7f9d9e6415d 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -2072,6 +2072,12 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) { const Expr *Arg = E->getArgument(); Address Ptr = EmitPointerWithAlignment(Arg); + // If this delete expression uses global ::operator delete (not a + // class-specific one), note it so we emit __global_delete forwarding bodies. + if (!isa<CXXMethodDecl>(E->getOperatorDelete()) && + 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/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 94a52f51118a7..3d47d62555239 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -992,6 +992,7 @@ void CodeGenModule::Release() { applyReplacements(); emitMultiVersionFunctions(); emitPFPFieldsWithEvaluatedOffset(); + emitGlobalDeleteForwardingBodies(); if (Context.getLangOpts().IncrementalExtensions && GlobalTopLevelStmtBlockInFlight.first) { @@ -8652,3 +8653,51 @@ void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) { // even if destructor is only declared. addDeferredDeclToEmit(VectorDtorGD); } + +void CodeGenModule::addPendingGlobalDelete( + StringRef GlobalDeleteName, const FunctionDecl *OperatorDeleteFD) { + // Only add if we haven't seen this name before. + for (const auto &Entry : PendingMSVCGlobalDeletes) + if (Entry.first == GlobalDeleteName) + return; + PendingMSVCGlobalDeletes.emplace_back(GlobalDeleteName.str(), + OperatorDeleteFD); +} + +void CodeGenModule::noteDirectGlobalDelete() { HasDirectGlobalDelete = true; } + +void CodeGenModule::emitGlobalDeleteForwardingBodies() { + // MSVC-compatible __global_delete forwarding bodies. + // + // Destructor helpers call __global_delete (a weak symbol defaulting to the + // trapping __empty_global_delete) instead of ::operator delete directly. + // When this TU contains a ::delete expression, 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 30e401a3e5d6c..5551cfce37aaa 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -534,6 +534,16 @@ 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. + /// Stores the __global_delete mangled name and the corresponding global + /// ::operator delete FunctionDecl, in insertion order. + llvm::SmallVector<std::pair<std::string, const FunctionDecl *>, 4> + 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; @@ -1591,6 +1601,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/microsoft-vector-deleting-dtors.cpp b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp index 4891ce12f8fed..e517e4e1ccac0 100644 --- a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp +++ b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp @@ -284,12 +284,20 @@ void kernelTest() { // 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 with no-op fallback) -// instead of directly referencing ::operator delete. This is critical for -// environments like kernel mode where no global ::operator delete exists. -// Verify __empty_global_delete is emitted as a no-op fallback. +// 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: _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
