Author: Daniel Paoliello Date: 2026-07-22T13:45:45Z New Revision: 71ee1456b23270f768571db9a4b060fc4f4b8569
URL: https://github.com/llvm/llvm-project/commit/71ee1456b23270f768571db9a4b060fc4f4b8569 DIFF: https://github.com/llvm/llvm-project/commit/71ee1456b23270f768571db9a4b060fc4f4b8569.diff LOG: [clang][win] Fix __global_delete wrappers for Arm64EC and cross-TU ::delete (#209585) PR #188372 made Clang's MSVC-ABI deleting-destructor path route global deletes through compiler-generated __global_delete / __global_array_delete wrappers instead of referencing ::operator delete directly. This lets a deleting destructor be emitted in environments (e.g. kernel mode) where no global ::operator delete exists: each wrapper defaults to a trapping __empty_global_delete fallback, and a real forwarding body that calls ::operator delete is materialized only when the program actually uses ::delete. This change refines how those wrappers and their fallbacks are emitted, fixing two problems that made the #188372 mechanism fail in practice. New behavior: - The trapping fallback is now emitted as a weak GlobalAlias to __empty_global_delete rather than via an /alternatename directive. This lowers to a COFF weak-external-with-default -- exactly what MSVC emits -- so a real __global_delete defined in any TU overrides the fallback in every other TU at link time. - A ::delete expression now registers the wrapper for its resolved global operator delete directly (in EmitCXXDeleteExpr), so the strong forwarding body is emitted in that TU regardless of where the class's deleting destructor is defined. This matches MSVC, which emits the forwarder at every ::delete site. Bugs fixed: 1. Arm64EC (miscompile -> LNK2019). /alternatename only names the plain symbol, not the backend-generated "$$h" hybrid EC symbol referenced by the exit thunk, so Arm64EC images left __global_delete$exit_thunk unresolved. The weak GlobalAlias correctly produces the "$$h" symbol, exit thunk, and plain alias (verified on both x64 and Arm64EC). 2. Cross-TU ::delete (runtime crash). Previously the forwarding body was only emitted in a TU that *both* emitted a matching vector deleting destructor and contained a ::delete. A TU that only performed `::delete p` -- with the class defined elsewhere -- emitted no forwarder, so nothing overrode the weak-alias trap and the program executed a trapping __empty_global_delete at runtime (STATUS_ILLEGAL_INSTRUCTION). Registering the wrapper at the ::delete site fixes this. To share wrapper-creation logic between the deleting-destructor path and the delete-expression path, getOrCreateMSVCGlobalDeleteWrapper is promoted from a static helper in CGClass.cpp to a CodeGenModule method. Verified end-to-end: a weak alias in one TU and a strong forwarder in another resolve to the forwarder under both lld-link and MSVC link.exe, and the previously-crashing delete-only TU now runs cleanly. Added: clang/test/CodeGenCXX/msvc-global-delete-forwarding-at-delete-site.cpp Modified: clang/lib/CodeGen/CGClass.cpp clang/lib/CodeGen/CGExprCXX.cpp clang/lib/CodeGen/CodeGenModule.cpp clang/lib/CodeGen/CodeGenModule.h clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp clang/test/CodeGenCXX/msvc-global-delete-scalar-array-split.cpp clang/test/CodeGenCXX/msvc-global-delete-scope-no-dtor.cpp clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp Removed: ################################################################################ diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index 70666dffdc903..39b8e50f68eaf 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -1410,112 +1410,6 @@ static bool CanSkipVTablePointerInitialization(CodeGenFunction &CGF, return true; } -/// Get or create the MSVC-compatible __global_delete wrapper function. -/// -/// Destructor helpers call __global_delete instead of ::operator delete -/// directly. If this TU contains a ::delete expression (or a dllexport class -/// whose deleting destructor takes the global-delete path), a real forwarding -/// body is emitted at end-of-file. If ::delete is never used anywhere in the -/// program, then no definition will exist and the `/ALTERNATENAME` linker -/// directive will cause the linker to use __empty_global_delete as the -/// definition. __empty_global_delete is never expected to actually be called, -/// hence it is a trap function (a deliberate deviation from MSVC, whose empty -/// is a no-op). -/// -/// Array delete[] uses a parallel __global_array_delete wrapper, matching -/// MSVC. The scalar and array wrappers of a given signature share a single -/// __empty_global_delete fallback. -static llvm::Constant * -getOrCreateMSVCGlobalDeleteWrapper(CodeGenModule &CGM, - const FunctionDecl *GlobOD) { - assert(CGM.getTarget().getCXXABI().isMicrosoft() && - "__global_delete wrapper is only used with the Microsoft ABI"); - llvm::Module &M = CGM.getModule(); - llvm::LLVMContext &LLVMCtx = M.getContext(); - - llvm::Constant *GlobDeleteCallee = CGM.GetAddrOfFunction(GlobOD); - auto *GlobDeleteFn = cast<llvm::Function>(GlobDeleteCallee); - llvm::FunctionType *FnTy = GlobDeleteFn->getFunctionType(); - - // Derive the wrapper and empty-fallback mangled names. MSVC uses distinct - // wrapper names for scalar vs array global delete, but a single shared empty - // fallback per signature: - // Global ::operator delete mangling: ??3@<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; - const char *WrapperBase; - if (GlobDeleteMangledName.starts_with("??3@")) { - Signature = GlobDeleteMangledName.substr(4); - WrapperBase = "?__global_delete@@"; - } else if (GlobDeleteMangledName.starts_with("??_V@")) { - Signature = GlobDeleteMangledName.substr(5); - WrapperBase = "?__global_array_delete@@"; - } else { - llvm_unreachable("unexpected global operator delete mangling"); - } - - std::string GlobalDeleteName = (WrapperBase + Signature).str(); - std::string EmptyGlobalDeleteName = - ("?__empty_global_delete@@" + Signature).str(); - - // Only set up the wrapper once per module. - if (llvm::Function *Existing = M.getFunction(GlobalDeleteName)) - return Existing; - - // Create the shared __empty_global_delete fallback if it doesn't already - // exist. The scalar and array wrappers of a given signature share one empty - // (matching MSVC, whose weak externals both point at a single - // __empty_global_delete). The body traps: this path is unreachable at - // runtime when ::delete is never used (a deliberate deviation from MSVC, - // whose empty is a no-op; see the doc comment above). - llvm::Function *EmptyFn = M.getFunction(EmptyGlobalDeleteName); - if (!EmptyFn) { - EmptyFn = llvm::Function::Create( - FnTy, llvm::GlobalValue::LinkOnceODRLinkage, EmptyGlobalDeleteName, &M); - EmptyFn->setComdat(M.getOrInsertComdat(EmptyGlobalDeleteName)); - EmptyFn->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); - CGM.SetLLVMFunctionAttributes( - GlobalDecl(GlobOD), - CGM.getTypes().arrangeGlobalDeclaration(GlobalDecl(GlobOD)), EmptyFn, - /*IsThunk=*/false); - CGM.SetLLVMFunctionAttributesForDefinition(GlobOD, EmptyFn); - CGM.getTargetCodeGenInfo().setTargetAttributes(GlobOD, EmptyFn, CGM); - auto *BB = llvm::BasicBlock::Create(LLVMCtx, "", EmptyFn); - llvm::Function *TrapFn = - llvm::Intrinsic::getOrInsertDeclaration(&M, llvm::Intrinsic::trap); - auto *TrapCall = llvm::CallInst::Create(TrapFn, {}, "", BB); - TrapCall->setDoesNotReturn(); - TrapCall->setDoesNotThrow(); - new llvm::UnreachableInst(LLVMCtx, BB); - - // Nothing directly uses the empty other than the /alternatename directive, - // so explicitly mark it as used. - appendToUsed(M, {EmptyFn}); - } - - // Emit /ALTERNATENAME linker directive: if this wrapper isn't provided, - // fall back to the trapping __empty_global_delete. - std::string AltOption = - "/alternatename:" + GlobalDeleteName + "=" + EmptyGlobalDeleteName; - auto *AltMD = - llvm::MDNode::get(LLVMCtx, {llvm::MDString::get(LLVMCtx, AltOption)}); - M.getOrInsertNamedMetadata("llvm.linker.options")->addOperand(AltMD); - - // Return the __global_delete wrapper function to call. - auto GlobalDeleteCallee = M.getOrInsertFunction(GlobalDeleteName, FnTy); - auto *GlobalDeleteFn = cast<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(GlobalDeleteFn, GlobOD); - - return GlobalDeleteFn; -} - static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, CodeGenFunction &CGF, llvm::Value *ShouldDeleteCondition) { @@ -1602,8 +1496,9 @@ static void EmitConditionalArrayDtorCall(const CXXDestructorDecl *DD, // 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()); + llvm::Constant *GlobalDeleteWrapper = + CGF.CGM.getOrCreateMSVCGlobalDeleteWrapper( + 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>()) @@ -1870,7 +1765,7 @@ void EmitConditionalDtorDeleteCall(CodeGenFunction &CGF, // ::operator delete to match MSVC's behavior. See the doc comment on // getOrCreateMSVCGlobalDeleteWrapper for details. llvm::Constant *GlobalDeleteWrapper = - getOrCreateMSVCGlobalDeleteWrapper(CGF.CGM, GlobOD); + CGF.CGM.getOrCreateMSVCGlobalDeleteWrapper(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>()) diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 52e0fdbc59a11..1769ab00ed56d 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -2108,8 +2108,20 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) { // 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()) + if (RD && RD->hasDefinition() && !RD->hasTrivialDestructor()) { CGM.noteDirectGlobalDelete(); + // Ensure a __global_delete wrapper (and thus a strong forwarding body) + // is emitted in THIS TU for the resolved global ::operator delete, even + // when no vector deleting destructor here references it. Without this, a + // TU that only does ::delete (with the deleting destructor defined in + // another TU) would emit no forwarder, leaving the wrapper bound to the + // trapping empty fallback and crashing at runtime. + const FunctionDecl *OD = E->getOperatorDelete(); + assert(!isa<CXXMethodDecl>(OD) && + "global ::delete should resolve to a namespace-scope " + "operator delete"); + CGM.getOrCreateMSVCGlobalDeleteWrapper(OD); + } } // Null check the pointer. diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 78627047b19ad..ca71458d85134 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -8906,45 +8906,159 @@ void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) { } void CodeGenModule::addPendingGlobalDelete( - llvm::Function *GlobalDeleteFn, const FunctionDecl *OperatorDeleteFD) { + llvm::GlobalAlias *GlobalDeleteAlias, + const FunctionDecl *OperatorDeleteFD) { // insert() is a no-op if this wrapper has already been recorded, keeping the // first FunctionDecl seen for it. - PendingMSVCGlobalDeletes.insert({GlobalDeleteFn, OperatorDeleteFD}); + PendingMSVCGlobalDeletes.insert({GlobalDeleteAlias, OperatorDeleteFD}); } void CodeGenModule::noteDirectGlobalDelete() { HasDirectGlobalDelete = 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 forwarding body is emitted and the wrapper defaults to a +/// weak alias to __empty_global_delete. __empty_global_delete is never +/// expected to actually be called, hence it is a trap function (a deliberate +/// deviation from MSVC, whose empty is a no-op). +/// +/// Array delete[] uses a parallel __global_array_delete wrapper, matching +/// MSVC. The scalar and array wrappers of a given signature share a single +/// __empty_global_delete fallback. +llvm::Constant * +CodeGenModule::getOrCreateMSVCGlobalDeleteWrapper(const FunctionDecl *GlobOD) { + assert(getTarget().getCXXABI().isMicrosoft() && + "__global_delete wrapper is only used with the Microsoft ABI"); + llvm::Module &M = getModule(); + llvm::LLVMContext &LLVMCtx = M.getContext(); + + llvm::Constant *GlobDeleteCallee = GetAddrOfFunction(GlobOD); + auto *GlobDeleteFn = cast<llvm::Function>(GlobDeleteCallee); + llvm::FunctionType *FnTy = GlobDeleteFn->getFunctionType(); + + // Derive the wrapper and empty-fallback mangled names. MSVC uses distinct + // wrapper names for scalar vs array global delete, but a single shared empty + // fallback per signature: + // Global ::operator delete mangling: ??3@<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; + const char *WrapperBase; + if (GlobDeleteMangledName.starts_with("??3@")) { + Signature = GlobDeleteMangledName.substr(4); + WrapperBase = "?__global_delete@@"; + } else if (GlobDeleteMangledName.starts_with("??_V@")) { + Signature = GlobDeleteMangledName.substr(5); + WrapperBase = "?__global_array_delete@@"; + } else { + llvm_unreachable("unexpected global operator delete mangling"); + } + + std::string GlobalDeleteName = (WrapperBase + Signature).str(); + std::string EmptyGlobalDeleteName = + ("?__empty_global_delete@@" + Signature).str(); + + // Only set up the wrapper once per module. The wrapper may be a weak alias + // (the default fallback) or, once replaced, a real forwarding function. + if (llvm::GlobalValue *Existing = M.getNamedValue(GlobalDeleteName)) + return Existing; + + // Create the shared __empty_global_delete fallback if it doesn't already + // exist. The scalar and array wrappers of a given signature share one empty + // (matching MSVC, whose weak externals both point at a single + // __empty_global_delete). The body traps: this path is unreachable at + // runtime when ::delete is never used (a deliberate deviation from MSVC, + // whose empty is a no-op; see the doc comment above). + llvm::Function *EmptyFn = M.getFunction(EmptyGlobalDeleteName); + if (!EmptyFn) { + EmptyFn = llvm::Function::Create( + FnTy, llvm::GlobalValue::LinkOnceODRLinkage, EmptyGlobalDeleteName, &M); + EmptyFn->setComdat(M.getOrInsertComdat(EmptyGlobalDeleteName)); + EmptyFn->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + SetLLVMFunctionAttributes( + GlobalDecl(GlobOD), + getTypes().arrangeGlobalDeclaration(GlobalDecl(GlobOD)), EmptyFn, + /*IsThunk=*/false); + SetLLVMFunctionAttributesForDefinition(GlobOD, EmptyFn); + getTargetCodeGenInfo().setTargetAttributes(GlobOD, EmptyFn, *this); + 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); + + // The empty is referenced only by the wrapper's weak alias. When this TU + // uses ::delete that alias is replaced by a real forwarding body, leaving + // the empty otherwise unreferenced, so explicitly mark it used to ensure + // it is always emitted (matching MSVC). + appendToUsed(M, {EmptyFn}); + } + + // The wrapper defaults to a weak alias to the trapping __empty_global_delete + // fallback (see the doc comment above for why this is a weak alias rather + // than an /alternatename directive). If this TU directly uses global + // ::operator delete, the alias is replaced with a real forwarding body in + // emitGlobalDeleteForwardingBodies(). + auto *GlobalDeleteAlias = llvm::GlobalAlias::create( + FnTy, GlobDeleteFn->getAddressSpace(), llvm::GlobalValue::WeakAnyLinkage, + GlobalDeleteName, EmptyFn, &M); + + // Register this variant so we can replace the alias with a real forwarding + // body at end-of-TU if this TU contains any direct use of global + // ::operator delete. + addPendingGlobalDelete(GlobalDeleteAlias, GlobOD); + + return GlobalDeleteAlias; +} + 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. + // path), we know ::operator delete must exist, so we replace the wrapper's + // weak alias-to-empty fallback with a real __global_delete definition that + // forwards to it. if (!HasDirectGlobalDelete) return; for (const auto &Entry : PendingMSVCGlobalDeletes) { - llvm::Function *GlobDelFn = Entry.first; - if (!GlobDelFn->isDeclaration()) - continue; - + llvm::GlobalAlias *Alias = Entry.first; const FunctionDecl *OperatorDeleteFD = Entry.second; llvm::Constant *RealDeleteFn = GetAddrOfFunction(OperatorDeleteFD); - // Create the forwarding body: call ::operator delete with all args. + // Create the strong forwarding function. Use LinkOnceODR so multiple TUs + // can emit this without conflicts. + auto *FnTy = cast<llvm::FunctionType>(Alias->getValueType()); + auto *GlobDelFn = + llvm::Function::Create(FnTy, llvm::GlobalValue::LinkOnceODRLinkage, + Alias->getAddressSpace(), "", &getModule()); + + // Emit 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::CallInst::Create(FnTy, RealDeleteFn, Args, "", BB); llvm::ReturnInst::Create(getModule().getContext(), BB); - // Use LinkOnceODR so multiple TUs can emit this without conflicts. - GlobDelFn->setLinkage(llvm::GlobalValue::LinkOnceODRLinkage); + // Replace the weak alias fallback with the real forwarding body, taking + // over its name. + Alias->replaceAllUsesWith(GlobDelFn); + GlobDelFn->takeName(Alias); + Alias->eraseFromParent(); + GlobDelFn->setComdat(getModule().getOrInsertComdat(GlobDelFn->getName())); SetLLVMFunctionAttributes( GlobalDecl(OperatorDeleteFD), diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 0abd75ccb0551..f62c761be0184 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -562,9 +562,9 @@ class CodeGenModule : public CodeGenTypeCache { llvm::SmallPtrSet<const CXXRecordDecl *, 16> RequireVectorDeletingDtor; /// Pending MSVC __global_delete variants that may need forwarding bodies. - /// Maps each __global_delete wrapper function to the corresponding global + /// Maps each __global_delete wrapper alias to the corresponding global /// ::operator delete FunctionDecl, in insertion order. - llvm::MapVector<llvm::Function *, const FunctionDecl *> + llvm::MapVector<llvm::GlobalAlias *, const FunctionDecl *> PendingMSVCGlobalDeletes; /// Whether this TU contains a direct use of global ::operator delete @@ -1654,9 +1654,16 @@ class CodeGenModule : public CodeGenTypeCache { void requireVectorDestructorDefinition(const CXXRecordDecl *RD); /// Record a pending __global_delete variant that may need a forwarding body. - void addPendingGlobalDelete(llvm::Function *GlobalDeleteFn, + void addPendingGlobalDelete(llvm::GlobalAlias *GlobalDeleteAlias, const FunctionDecl *OperatorDeleteFD); + /// Get or create the MSVC-compatible __global_delete wrapper for the given + /// global ::operator delete, registering it as a pending variant so a + /// forwarding body can be emitted if this TU directly uses global + /// ::operator delete. + llvm::Constant * + getOrCreateMSVCGlobalDeleteWrapper(const FunctionDecl *GlobOD); + /// Note that global ::operator delete is directly used in this TU. void noteDirectGlobalDelete(); diff --git a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp index da891a138739c..ba1760b49f2c9 100644 --- a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp +++ b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors.cpp @@ -196,6 +196,14 @@ void kernelTest() { // CHECK: delete.end: // CHECK-NEXT: ret void +// The __empty_global_delete fallback is emitted at the first ::delete site, +// which here lands before the deleting-destructor helpers. Verify it traps +// (the fallback path is unreachable at runtime once a real forwarding body is +// linked in). +// X64: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// X64-NEXT: call void @llvm.trap() +// X64-NEXT: unreachable + // Vector dtor definition for Parrot. // X64-LABEL: define weak dso_local noundef ptr @"??_EParrot@@UEAAPEAXI@Z"( // X64-SAME: ptr {{.*}} %[[THIS:.*]], i32 {{.*}} %[[IMPLICIT_PARAM:.*]]) unnamed_addr @@ -287,16 +295,6 @@ void kernelTest() { // destructor calls __global_delete instead of directly // referencing ::operator delete. This is critical for environments like // kernel mode where no global ::operator delete exists. -// Verify __empty_global_delete traps (the code path is unreachable at runtime). -// X64: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) -// X64-NEXT: call void @llvm.trap() -// X64-NEXT: unreachable - -// Verify that when ::delete is used in the TU, a real __global_array_delete -// forwarding body is emitted that calls through to the actual ::operator delete[]. -// X64: define linkonce_odr void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) -// X64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr %0, i64 %1) -// X64-NEXT: ret void // X64-LABEL: define weak dso_local noundef ptr @"??_EKernelDerived@@UEAAPEAXI@Z" // Verify the array delete path in the VDD uses __global_array_delete. @@ -394,7 +392,14 @@ void foobartest() { // 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"} +// The forwarding bodies for the global-delete wrappers are emitted at +// end-of-module (after all the deleting-destructor helpers). Because ::delete[] +// is used in the TU, both wrappers get real forwarding bodies: the array +// __global_array_delete forwards to ::operator delete[] (??_V@) and the scalar +// __global_delete forwards to ::operator delete (??3@). +// X64: define linkonce_odr void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// X64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr %0, i64 %1) +// X64: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// X64-NEXT: call void @"??3@YAXPEAX_K@Z"(ptr %0, i64 %1) // 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 0cb596b9a5717..b2f3a1b14d04d 100644 --- a/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp +++ b/clang/test/CodeGenCXX/microsoft-vector-deleting-dtors2.cpp @@ -94,12 +94,14 @@ void TesttheTest() { // X64: define linkonce_odr dso_local void @"??_V?$RefCounted@UDrawingBuffer@@@@SAXPEAX@Z"(ptr noundef %p) // X86: define linkonce_odr dso_local void @"??_V?$RefCounted@UDrawingBuffer@@@@SAXPAX@Z"(ptr noundef %p) -// Verify that the dllexport class triggers __global_array_delete forwarding -// body emission even without a ::delete expression in the TU. +// X86: define linkonce_odr dso_local x86_thiscallcc noundef ptr @"??_GNoExport@@UAEPAXI@Z"(ptr noundef nonnull align 4 dereferenceable(4) %this, i32 noundef %should_call_delete) +// X64: define linkonce_odr dso_local noundef ptr @"??_GNoExport@@UEAAPEAXI@Z"(ptr noundef nonnull align 8 dereferenceable(8) %this, i32 noundef %should_call_delete) + +// The wrapper forwarding bodies are emitted at end-of-module. Verify that the +// dllexport class triggers __global_array_delete forwarding body emission even +// without a ::delete expression in the TU. // X64: define linkonce_odr void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) // X64-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr %0, i64 %1) // X64-NEXT: ret void -// X86: define linkonce_odr dso_local x86_thiscallcc noundef ptr @"??_GNoExport@@UAEPAXI@Z"(ptr noundef nonnull align 4 dereferenceable(4) %this, i32 noundef %should_call_delete) -// X64: define linkonce_odr dso_local noundef ptr @"??_GNoExport@@UEAAPEAXI@Z"(ptr noundef nonnull align 8 dereferenceable(8) %this, i32 noundef %should_call_delete) // CHECK-NOT: define {{.*}}_V{{.*}}NoExport diff --git a/clang/test/CodeGenCXX/msvc-global-delete-forwarding-at-delete-site.cpp b/clang/test/CodeGenCXX/msvc-global-delete-forwarding-at-delete-site.cpp new file mode 100644 index 0000000000000..e4a254f6d2ae8 --- /dev/null +++ b/clang/test/CodeGenCXX/msvc-global-delete-forwarding-at-delete-site.cpp @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -emit-llvm -fms-extensions %s -triple=x86_64-pc-windows-msvc -o - | FileCheck %s + +// A `::delete` on a class whose deleting destructor is NOT defined in this TU +// (only declared, e.g. defined in another TU) must still emit a strong +// __global_delete forwarding body HERE. MSVC emits the forwarder at every +// `::delete` site (validated against cl.exe). Without this, a TU that only +// performs `::delete` (with the vector deleting destructor emitted in a +// diff erent TU) would emit no forwarder, leaving the wrapper bound to the +// trapping __empty_global_delete fallback and crashing at runtime. + +struct W { + // Declared, but ~W() is defined in another TU, so no vector deleting + // destructor (and hence no __global_delete wrapper reference) is emitted in + // this TU. The forwarder must come from the ::delete site below. + virtual ~W(); + void operator delete(void *); + int x; +}; + +void sink(W *p) { ::delete p; } + +// The shared trapping fallback is emitted (kept alive via llvm.used). +// CHECK: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// CHECK-NEXT: call void @llvm.trap() +// CHECK-NEXT: unreachable + +// The scalar wrapper gets a real forwarding body that calls the global +// ::operator delete (??3@), even though no deleting destructor in this TU +// references the wrapper. +// CHECK: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// CHECK-NEXT: call void @"??3@YAXPEAX_K@Z"(ptr %0, i64 %1) + +// No vector deleting destructor for W is emitted in this TU. +// CHECK-NOT: define {{.*}}@"??_EW@@ diff --git a/clang/test/CodeGenCXX/msvc-global-delete-scalar-array-split.cpp b/clang/test/CodeGenCXX/msvc-global-delete-scalar-array-split.cpp index 1ff622b0465bb..b558b2e6c78d0 100644 --- a/clang/test/CodeGenCXX/msvc-global-delete-scalar-array-split.cpp +++ b/clang/test/CodeGenCXX/msvc-global-delete-scalar-array-split.cpp @@ -43,21 +43,22 @@ void test() { ::delete[] a; } +// The shared __empty_global_delete fallback is emitted with a trapping body +// (kept alive via llvm.used) even though both wrappers have real forwarding +// bodies here. +// CHECK: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// CHECK-NEXT: call void @llvm.trap() + // The scalar deleting destructor's global path calls __global_delete. // CHECK: call void @"?__global_delete@@YAXPEAX_K@Z"( -// It forwards to the scalar ??3@ (::operator delete). -// CHECK: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) -// CHECK-NEXT: call void @"??3@YAXPEAX_K@Z"(ptr %0, i64 %1) - // The vector deleting destructor's global array path calls __global_array_delete. // CHECK: call void @"?__global_array_delete@@YAXPEAX_K@Z"( -// It forwards to the array ??_V@ (::operator delete[]). +// The forwarding bodies are emitted at end-of-module. Each wrapper forwards to +// its OWN global operator delete: __global_delete to the scalar ??3@, +// __global_array_delete to the array ??_V@. +// CHECK: define linkonce_odr void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) +// CHECK-NEXT: call void @"??3@YAXPEAX_K@Z"(ptr %0, i64 %1) // CHECK: define linkonce_odr void @"?__global_array_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) // CHECK-NEXT: call void @"??_V@YAXPEAX_K@Z"(ptr %0, i64 %1) - -// Both wrappers share a single __empty_global_delete fallback, wired via -// /ALTERNATENAME. -// CHECK-DAG: !{!"/alternatename:?__global_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} -// CHECK-DAG: !{!"/alternatename:?__global_array_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} diff --git a/clang/test/CodeGenCXX/msvc-global-delete-scope-no-dtor.cpp b/clang/test/CodeGenCXX/msvc-global-delete-scope-no-dtor.cpp index df0c2908267f0..09941600defa9 100644 --- a/clang/test/CodeGenCXX/msvc-global-delete-scope-no-dtor.cpp +++ b/clang/test/CodeGenCXX/msvc-global-delete-scope-no-dtor.cpp @@ -10,12 +10,12 @@ // __global_delete machinery when a deleting destructor is involved, i.e. for a // `::delete` on a class type with a non-trivial destructor. A `::delete` on a // primitive or on a trivially-destructible class is lowered as a plain direct -// operator delete, so MSVC leaves __global_delete as an unresolved weak -// external that falls back to the trapping __empty_global_delete via -// /ALTERNATENAME. (The destructor's virtualness and whether the class has its -// own operator delete are irrelevant to this trigger.) Emitting a forwarding -// body here would add a hard reference to the global operator delete and could -// reintroduce LNK2001 in environments without one. +// operator delete, so MSVC leaves __global_delete as a weak external that falls +// back to the trapping __empty_global_delete. Clang models this with a weak +// alias to __empty_global_delete. (The destructor's virtualness and whether the +// class has its own operator delete are irrelevant to this trigger.) Emitting a +// forwarding body here would add a hard reference to the global operator delete +// and could reintroduce LNK2001 in environments without one. struct Base { void *operator new[](__SIZE_TYPE__); @@ -50,6 +50,12 @@ void scopeTrivial(Trivial *q) { ::delete q; } +// The __global_array_delete wrapper defaults to a weak alias to the trapping +// fallback (no forwarding body of its own is emitted, asserted via the +// --implicit-check-not on the RUN line). The alias is emitted before the +// function definitions. +// CHECK: @"?__global_array_delete@@YAXPEAX_K@Z" = weak alias void (ptr, i64), ptr @"?__empty_global_delete@@YAXPEAX_K@Z" + // The vector deleting destructor still routes its global array-delete path // through the __global_array_delete wrapper. // CHECK: call void @"?__global_array_delete@@YAXPEAX_K@Z" @@ -59,8 +65,3 @@ void scopeTrivial(Trivial *q) { // CHECK: define linkonce_odr void @"?__empty_global_delete@@YAXPEAX_K@Z"(ptr noundef %0, i64 noundef %1) // CHECK-NEXT: call void @llvm.trap() // CHECK-NEXT: unreachable - -// The /ALTERNATENAME directive wires __global_array_delete to the trapping -// fallback; no forwarding body of its own is emitted (asserted via the -// --implicit-check-not on the RUN line). -// CHECK: !{!"/alternatename:?__global_array_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} diff --git a/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp b/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp index a4ac2610926cd..1703b81c7f7f7 100644 --- a/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp +++ b/clang/test/CodeGenCXX/msvc-no-global-delete-forwarding.cpp @@ -24,6 +24,13 @@ void test() { delete[] p; } +// Each wrapper defaults to a weak alias to the trapping __empty_global_delete +// (matching MSVC's weak-external-with-default; this also works under Arm64EC, +// unlike an /alternatename directive). These aliases are emitted before the +// function definitions. +// CHECK-DAG: @"?__global_delete@@YAXPEAX_K@Z" = weak alias void (ptr, i64), ptr @"?__empty_global_delete@@YAXPEAX_K@Z" +// CHECK-DAG: @"?__global_array_delete@@YAXPEAX_K@Z" = weak alias void (ptr, i64), ptr @"?__empty_global_delete@@YAXPEAX_K@Z" + // The VDD dispatches between class and global delete: the array path uses the // __global_array_delete wrapper, the scalar path uses __global_delete. // CHECK-LABEL: define weak dso_local noundef ptr @"??_EDerived@@UEAAPEAXI@Z" @@ -43,6 +50,3 @@ void test() { // type in this TU, and no dllexport class). // CHECK-NOT: define {{.*}}void @"?__global_delete@@YAXPEAX_K@Z" // CHECK-NOT: define {{.*}}void @"?__global_array_delete@@YAXPEAX_K@Z" - -// Verify the /ALTERNATENAME linker directive. -// CHECK: !{!"/alternatename:?__global_delete@@YAXPEAX_K@Z=?__empty_global_delete@@YAXPEAX_K@Z"} _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
