[clang] [clang][win] MSVC-compat: Use `__global_delete` wrapper in deleting destructors instead of directly referencing `::operator delete` (PR #188372)
Daniel Paoliello via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 8 11:48:44 PDT 2026
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 <danpao at microsoft.com>
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 at YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef 48)
-// CLANG22-MSABI32: call void @"??3 at YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 32, i32 noundef 16)
+// CLANG22-MSABI64: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %{{.*}}, i64 noundef 48)
+// CLANG22-MSABI32: call void @"?__global_delete@@YAXPAXIW4align_val_t at 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 at YAXPEAX_KW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i64 noundef 96, i64 noundef 32)
-// CLANG22-MSABI32: call void @"??3 at YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 64, i32 noundef 32)
+// CLANG22-MSABI64: call void @"?__global_delete@@YAXPEAX_KW4align_val_t at std@@@Z"(ptr noundef %{{.*}}, i64 noundef 96, i64 noundef 32)
+// CLANG22-MSABI32: call void @"?__global_delete@@YAXPAXIW4align_val_t at 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 at YAXPAX@Z"(ptr %[[THIS]])
+// DTORS-NEXT: call void @"?__global_delete@@YAXPAX at 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 at Z" = weak dso_local unnamed_addr alias ptr (ptr, i32), ptr @"??_GBird@@UEAAPEAXI at 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 at YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
-// X86-NEXT: call void @"??_V at YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
+// X64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
+// X86-NEXT: call void @"?__global_delete@@YAXPAXI at 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 at 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 at Z"(ptr %0, i64 %1)
+// X64-NEXT: call void @"??_V at YAXPEAX_K@Z"(ptr %0, i64 %1)
+// X64-NEXT: ret void
+
+// X64-LABEL: define weak dso_local noundef ptr @"??_EKernelDerived@@UEAAPEAXI at 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 at 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 at Z"(ptr noundef %{{.*}}, i64 noundef 8)
+// X64: dtor.call_class_delete:
+// X64-NEXT: call void @"??3KernelBase@@SAXPEAX at Z"(ptr noundef %{{.*}})
struct BaseDelete1 {
@@ -346,3 +393,8 @@ void foobartest() {
// X64: define weak dso_local noundef ptr @"??_EAllocatedAsArray@@UEAAPEAXI at Z"
// X86: define weak dso_local x86_thiscallcc noundef ptr @"??_EAllocatedAsArray@@UAEPAXI at Z"
// CLANG21: define linkonce_odr dso_local noundef ptr @"??_GAllocatedAsArray@@UEAAPEAXI at Z"
+
+// Verify the /ALTERNATENAME linker directive.
+// X64: !{!"/alternatename:?__global_delete@@YAXPEAX_K at Z=?__empty_global_delete@@YAXPEAX_K at 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 at Z"
// X64: call void @"??1DrawingBuffer@@UEAA at XZ"(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %arraydestroy.element)
// X64: call void @"??_V?$RefCounted at UDrawingBuffer@@@@SAXPEAX at Z"(ptr noundef %2)
-// X64: call void @"??_V at YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %{{.*}})
+// X64: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %{{.*}})
// X64: call void @"??1DrawingBuffer@@UEAA at XZ"(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %this1)
// X64: call void @"??3 at 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 at Z"
// X86: call x86_thiscallcc void @"??1DrawingBuffer@@UAE at XZ"(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %arraydestroy.element)
// X86: call void @"??_V?$RefCounted at UDrawingBuffer@@@@SAXPAX at Z"(ptr noundef %2)
-// X86: call void @"??_V at YAXPAXI@Z"(ptr noundef %2, i32 noundef {{.*}})
+// X86: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef {{.*}})
// X86 call x86_thiscallcc void @"??1DrawingBuffer@@UAE at XZ"(ptr noundef nonnull align 4 dereferenceable(4) %this1)
// X86: call void @"??3 at YAXPAXI@Z"(ptr noundef %this1, i32 noundef {{.*}})
@@ -94,6 +94,12 @@ void TesttheTest() {
// X64: define linkonce_odr dso_local void @"??_V?$RefCounted at UDrawingBuffer@@@@SAXPEAX at Z"(ptr noundef %p)
// X86: define linkonce_odr dso_local void @"??_V?$RefCounted at UDrawingBuffer@@@@SAXPAX at 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 at Z"(ptr %0, i64 %1)
+// X64-NEXT: call void @"??_V at YAXPEAX_K@Z"(ptr %0, i64 %1)
+// X64-NEXT: ret void
+
// X86: define linkonce_odr dso_local x86_thiscallcc noundef ptr @"??_GNoExport@@UAEPAXI at Z"(ptr noundef nonnull align 4 dereferenceable(4) %this, i32 noundef %should_call_delete)
// X64: define linkonce_odr dso_local noundef ptr @"??_GNoExport@@UEAAPEAXI at 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 at Z"
+// CHECK: dtor.call_glob_delete_after_array_destroy:
+// CHECK: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %{{.*}}, i64 noundef %{{.*}})
+// CHECK: dtor.call_glob_delete:
+// CHECK-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %{{.*}}, i64 noundef 8)
+// CHECK: dtor.call_class_delete:
+// CHECK-NEXT: call void @"??3Base@@SAXPEAX at Z"(ptr noundef %{{.*}})
+
+// __empty_global_delete should be emitted with a trap.
+// CHECK: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K at 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 at Z"
+
+// Verify the /ALTERNATENAME linker directive.
+// CHECK: !{!"/alternatename:?__global_delete@@YAXPEAX_K at Z=?__empty_global_delete@@YAXPEAX_K at 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 at YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ1]])
-// X86-NEXT: call void @"??_V at YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ1]])
+// X64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ1]])
+// X86-NEXT: call void @"?__global_delete@@YAXPAXI at 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 at YAXPAXI@Z"
-// CHECK64-NEXT: call void @"??3 at YAXPEAX_K@Z"
+// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI at Z"
+// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at 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 at YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
-// CHECK64-NEXT: call void @"??_V at YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
+// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
+// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
// CHECK: dtor.call_glob_delete:
-// CHECK32-NEXT: call void @"??3 at YAXPAXI@Z"(ptr noundef %this1, i32 noundef 8)
-// CHECK64-NEXT: call void @"??3 at YAXPEAX_K@Z"(ptr noundef %this1, i64 noundef 16)
+// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %this1, i32 noundef 8)
+// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %this1, i64 noundef 16)
// CHECK: dtor.call_class_delete:
// CHECK32-NEXT: call void @"??3Base2@@SAXPAX at Z"(ptr noundef %this1)
// CHECK64-NEXT: call void @"??3Base2@@SAXPEAX at 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 at YAXPAXI@Z"
-// CHECK64-NEXT: call void @"??3 at YAXPEAX_K@Z"
+// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI at Z"
+// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at 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 at YAXPAXI@Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
-// CHECK64-NEXT: call void @"??_V at YAXPEAX_K@Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
+// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
+// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
// CHECK: dtor.call_glob_delete:
-// CHECK32-NEXT: call void @"??3 at YAXPAXI@Z"(ptr noundef %this1, i32 noundef 8)
-// CHECK64-NEXT: call void @"??3 at YAXPEAX_K@Z"(ptr noundef %this1, i64 noundef 16)
+// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %this1, i32 noundef 8)
+// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %this1, i64 noundef 16)
// CHECK: dtor.call_class_delete:
// CHECK32-NEXT: call void @"??3Base2@@SAXPAX at Z"(ptr noundef %this1)
// CHECK64-NEXT: call void @"??3Base2@@SAXPEAX at Z"(ptr noundef %this1)
>From dc3e5fa9c504901b86a5e76d9e6fe2628ae37aee Mon Sep 17 00:00:00 2001
From: Daniel Paoliello <danpao at microsoft.com>
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 <danpao at microsoft.com>
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 at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
-// X86-NEXT: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
+// X64-NEXT: call void @"?__global_array_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
+// X86-NEXT: call void @"?__global_array_delete@@YAXPAXI at 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 at 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 at Z"(ptr %0, i64 %1)
// X64-NEXT: call void @"??_V at YAXPEAX_K@Z"(ptr %0, i64 %1)
// X64-NEXT: ret void
// X64-LABEL: define weak dso_local noundef ptr @"??_EKernelDerived@@UEAAPEAXI at 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 at Z"(ptr noundef %{{.*}}, i64 noundef %{{.*}})
+// X64: call void @"?__global_array_delete@@YAXPEAX_K at 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 at Z"
// X64: call void @"??1DrawingBuffer@@UEAA at XZ"(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %arraydestroy.element)
// X64: call void @"??_V?$RefCounted at UDrawingBuffer@@@@SAXPEAX at Z"(ptr noundef %2)
-// X64: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %{{.*}})
+// X64: call void @"?__global_array_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %{{.*}})
// X64: call void @"??1DrawingBuffer@@UEAA at XZ"(ptr noundef nonnull align 8 dead_on_return(8) dereferenceable(8) %this1)
// X64: call void @"??3 at 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 at Z"
// X86: call x86_thiscallcc void @"??1DrawingBuffer@@UAE at XZ"(ptr noundef nonnull align 4 dead_on_return(4) dereferenceable(4) %arraydestroy.element)
// X86: call void @"??_V?$RefCounted at UDrawingBuffer@@@@SAXPAX at Z"(ptr noundef %2)
-// X86: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef {{.*}})
+// X86: call void @"?__global_array_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef {{.*}})
// X86 call x86_thiscallcc void @"??1DrawingBuffer@@UAE at XZ"(ptr noundef nonnull align 4 dereferenceable(4) %this1)
// X86: call void @"??3 at YAXPAXI@Z"(ptr noundef %this1, i32 noundef {{.*}})
@@ -94,9 +94,9 @@ void TesttheTest() {
// X64: define linkonce_odr dso_local void @"??_V?$RefCounted at UDrawingBuffer@@@@SAXPEAX at Z"(ptr noundef %p)
// X86: define linkonce_odr dso_local void @"??_V?$RefCounted at UDrawingBuffer@@@@SAXPAX at 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 at 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 at Z"(ptr %0, i64 %1)
// X64-NEXT: call void @"??_V at 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 at Z"
// CHECK: dtor.call_glob_delete_after_array_destroy:
-// CHECK: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %{{.*}}, i64 noundef %{{.*}})
+// CHECK: call void @"?__global_array_delete@@YAXPEAX_K at Z"(ptr noundef %{{.*}}, i64 noundef %{{.*}})
// CHECK: dtor.call_glob_delete:
// CHECK-NEXT: call void @"?__global_delete@@YAXPEAX_K at 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 at Z"
+// CHECK-NOT: define {{.*}}void @"?__global_array_delete@@YAXPEAX_K at Z"
// Verify the /ALTERNATENAME linker directive.
// CHECK: !{!"/alternatename:?__global_delete@@YAXPEAX_K at Z=?__empty_global_delete@@YAXPEAX_K at 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 at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ1]])
-// X86-NEXT: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef %[[TOTALSZ1]])
+// X64-NEXT: call void @"?__global_array_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ1]])
+// X86-NEXT: call void @"?__global_array_delete@@YAXPAXI at 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 at Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
-// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
+// CHECK32-NEXT: call void @"?__global_array_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
+// CHECK64-NEXT: call void @"?__global_array_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
// CHECK: dtor.call_glob_delete:
// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %this1, i32 noundef 8)
// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at 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 at Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
-// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
+// CHECK32-NEXT: call void @"?__global_array_delete@@YAXPAXI at Z"(ptr noundef %2, i32 noundef %[[TOTALSZ]])
+// CHECK64-NEXT: call void @"?__global_array_delete@@YAXPEAX_K at Z"(ptr noundef %2, i64 noundef %[[TOTALSZ]])
// CHECK: dtor.call_glob_delete:
// CHECK32-NEXT: call void @"?__global_delete@@YAXPAXI at Z"(ptr noundef %this1, i32 noundef 8)
// CHECK64-NEXT: call void @"?__global_delete@@YAXPEAX_K at Z"(ptr noundef %this1, i64 noundef 16)
More information about the cfe-commits
mailing list