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

Reply via email to