Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions clang/docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,17 @@ 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`` (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``
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
Expand Down
136 changes: 132 additions & 4 deletions clang/lib/CodeGen/CGClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -1409,6 +1410,112 @@ 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how much practical effect it has here, but the general pattern is to call CGM.SetLLVMFunctionAttributes(), then CGM.SetLLVMFunctionAttributesForDefinition(), then getTargetCodeGenInfo().setTargetAttributes(). (We should really clean this up at some point...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

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) {
Expand Down Expand Up @@ -1492,9 +1599,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.
Expand Down Expand Up @@ -1721,9 +1837,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
Expand All @@ -1747,7 +1866,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);
Expand Down
28 changes: 24 additions & 4 deletions clang/lib/CodeGen/CGExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2092,6 +2095,23 @@ void CodeGenFunction::EmitCXXDeleteExpr(const CXXDeleteExpr *E) {
const Expr *Arg = E->getArgument();
Address Ptr = EmitPointerWithAlignment(Arg);

// 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.
//
// We could avoid this null check if we can determine that the object
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -3306,7 +3306,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);
Expand Down
52 changes: 52 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,7 @@ void CodeGenModule::Release() {
applyReplacements();
emitMultiVersionFunctions();
emitPFPFieldsWithEvaluatedOffset();
emitGlobalDeleteForwardingBodies();

if (Context.getLangOpts().IncrementalExtensions &&
GlobalTopLevelStmtBlockInFlight.first) {
Expand Down Expand Up @@ -8910,3 +8911,54 @@ void CodeGenModule::requireVectorDestructorDefinition(const CXXRecordDecl *RD) {
// even if destructor is only declared.
addDeferredDeclToEmit(VectorDtorGD);
}

void CodeGenModule::addPendingGlobalDelete(
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({GlobalDeleteFn, 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 = Entry.first;
if (!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()));
SetLLVMFunctionAttributes(
GlobalDecl(OperatorDeleteFD),
getTypes().arrangeGlobalDeclaration(GlobalDecl(OperatorDeleteFD)),
GlobDelFn, /*IsThunk=*/false);
SetLLVMFunctionAttributesForDefinition(OperatorDeleteFD, GlobDelFn);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Same issue with attributes here.)

getTargetCodeGenInfo().setTargetAttributes(OperatorDeleteFD, GlobDelFn,
*this);
}
}
21 changes: 21 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,16 @@ class CodeGenModule : public CodeGenTypeCache {
/// was emitted for the class.
llvm::SmallPtrSet<const CXXRecordDecl *, 16> RequireVectorDeletingDtor;

/// Pending MSVC __global_delete variants that may need forwarding bodies.
/// Maps each __global_delete wrapper function to the corresponding global
/// ::operator delete FunctionDecl, in insertion order.
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).
bool HasDirectGlobalDelete = false;

typedef std::pair<OrderGlobalInitsOrStermFinalizers, llvm::Function *>
GlobalInitData;

Expand Down Expand Up @@ -1643,6 +1653,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(llvm::Function *GlobalDeleteFn,
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);

Expand Down
8 changes: 4 additions & 4 deletions clang/test/CodeGenCXX/cxx2a-destroying-delete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ H::~H() { call_in_dtor(); }
// CLANG22-MSABI-NEXT: br i1 %[[CHCK2]], label %dtor.call_class_delete, label %dtor.call_glob_delete
//
// CLANG22-MSABI-LABEL: dtor.call_glob_delete:
// CLANG22-MSABI64: call void @"??3@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef 48)
// CLANG22-MSABI32: call void @"??3@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 32, i32 noundef 16)
// CLANG22-MSABI64: call void @"?__global_delete@@YAXPEAX_K@Z"(ptr noundef %{{.*}}, i64 noundef 48)
// CLANG22-MSABI32: call void @"?__global_delete@@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 32, i32 noundef 16)
// CLANG22-MSABI-NEXT: br label %[[RETURN:.*]]
//
// CLANG21-MSABI: dtor.call_delete:
Expand Down Expand Up @@ -284,8 +284,8 @@ I::~I() { call_in_dtor(); }
// CLANG22-MSABI-NEXT: br i1 %[[CHCK2]], label %dtor.call_class_delete, label %dtor.call_glob_delete
//
// CLANG22-MSABI: dtor.call_glob_delete:
// CLANG22-MSABI64: call void @"??3@YAXPEAX_KW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i64 noundef 96, i64 noundef 32)
// CLANG22-MSABI32: call void @"??3@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 64, i32 noundef 32)
// CLANG22-MSABI64: call void @"?__global_delete@@YAXPEAX_KW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i64 noundef 96, i64 noundef 32)
// CLANG22-MSABI32: call void @"?__global_delete@@YAXPAXIW4align_val_t@std@@@Z"(ptr noundef %{{.*}}, i32 noundef 64, i32 noundef 32)
// CLANG22-MSABI-NEXT: br label %[[RETURN:.*]]
//
// CLANG21-MSABI: dtor.call_delete:
Expand Down
2 changes: 1 addition & 1 deletion clang/test/CodeGenCXX/microsoft-abi-structors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ void checkH() {
// DTORS-NEXT: br i1 %[[CONDITION1]], label %[[CALL_CLASS_DELETE:[0-9a-z._]+]], label %[[CALL_GLOB_DELETE:[0-9a-z._]+]]
//
// DTORS: [[CALL_GLOB_DELETE]]
// DTORS-NEXT: call void @"??3@YAXPAX@Z"(ptr %[[THIS]])
// DTORS-NEXT: call void @"?__global_delete@@YAXPAX@Z"(ptr %[[THIS]])
// DTORS-NEXT: br label %[[CONTINUE_LABEL]]
//
// DTORS: [[CALL_CLASS_DELETE]]
Expand Down
Loading