[clang] e7924d5 - [clang] Handle constructor closures with consteval default args (#203554)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 2 00:36:02 PDT 2026
Author: Hans Wennborg
Date: 2026-07-02T09:35:57+02:00
New Revision: e7924d50db0ae1c584c975fc4f274960d93c92d2
URL: https://github.com/llvm/llvm-project/commit/e7924d50db0ae1c584c975fc4f274960d93c92d2
DIFF: https://github.com/llvm/llvm-project/commit/e7924d50db0ae1c584c975fc4f274960d93c92d2.diff
LOG: [clang] Handle constructor closures with consteval default args (#203554)
Fixes https://github.com/llvm/llvm-project/issues/201320
Just grabbing the default argument with `getDefaultArg()` during codegen
doesn't work if the expression requires evaluating a consteval
expression (see bug). Instead, we must properly BuildCXXDefaultArgExpr
it during Sema, store it in the AST (including
serialization/deserialization) and then use that during codegen.
Added:
clang/test/SemaCXX/ms-ctor-closure.cpp
Modified:
clang/include/clang/AST/ASTContext.h
clang/include/clang/AST/DeclCXX.h
clang/include/clang/Sema/Sema.h
clang/lib/AST/ASTContext.cpp
clang/lib/AST/DeclCXX.cpp
clang/lib/CodeGen/MicrosoftCXXABI.cpp
clang/lib/Sema/SemaDecl.cpp
clang/lib/Sema/SemaDeclCXX.cpp
clang/lib/Sema/SemaExprCXX.cpp
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
clang/lib/Serialization/ASTReaderDecl.cpp
clang/lib/Serialization/ASTWriterDecl.cpp
clang/test/CodeGenCXX/dllexport-ctor-closure.cpp
clang/test/CodeGenCXX/microsoft-abi-throw.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index a4ed852d36442..c04b380f9ec5b 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -514,6 +514,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Declaration for the CUDA cudaLaunchDevice function.
FunctionDecl *cudaLaunchDeviceDecl = nullptr;
+ llvm::DenseMap<const CXXConstructorDecl *, ArrayRef<CXXDefaultArgExpr *>>
+ CtorClosureDefaultArgs;
+
/// Keeps track of all declaration attributes.
///
/// Since so few decls have attrs, we keep them in a hash map instead of
@@ -1148,6 +1151,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Erase the attributes corresponding to the given declaration.
void eraseDeclAttrs(const Decl *D);
+ ArrayRef<CXXDefaultArgExpr *>
+ getCtorClosureDefaultArgs(const CXXConstructorDecl *CD);
+ void setCtorClosureDefaultArgs(const CXXConstructorDecl *CD,
+ ArrayRef<CXXDefaultArgExpr *> Args);
+
/// Get all ExplicitInstantiationDecls for a given specialization.
ArrayRef<ExplicitInstantiationDecl *>
getExplicitInstantiationDecls(const NamedDecl *Spec) const;
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 28d171253dc03..4bf190e311395 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -2877,6 +2877,9 @@ class CXXConstructorDecl final
return const_cast<CXXConstructorDecl*>(this)->getCanonicalDecl();
}
+ ArrayRef<CXXDefaultArgExpr *> getCtorClosureDefaultArgs() const;
+ void setCtorClosureDefaultArgs(ArrayRef<CXXDefaultArgExpr *> Args);
+
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == CXXConstructor; }
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 2df85771573e2..f7d0d493e7081 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14260,11 +14260,8 @@ class Sema final : public SemaBase {
LateInstantiatedAttrVec *LateAttrs = nullptr,
LocalInstantiationScope *OuterMostScope = nullptr);
- /// In the MS ABI, we need to instantiate default arguments of dllexported
- /// default constructors along with the constructor definition. This allows IR
- /// gen to emit a constructor closure which calls the default constructor with
- /// its default arguments.
- void InstantiateDefaultCtorDefaultArgs(CXXConstructorDecl *Ctor);
+ bool BuildCtorClosureDefaultArgs(SourceLocation Loc, CXXConstructorDecl *Ctor,
+ bool IsCopy = false);
bool InstantiateDefaultArgument(SourceLocation CallLoc, FunctionDecl *FD,
ParmVarDecl *Param);
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index abf0cd5e18c2b..a6eb87ab7d8aa 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -989,6 +989,8 @@ void ASTContext::cleanup() {
A->second->~AttrVec();
DeclAttrs.clear();
+ CtorClosureDefaultArgs.clear();
+
for (const auto &Value : ModuleInitializers)
Value.second->~PerModuleInitializers();
ModuleInitializers.clear();
@@ -1547,6 +1549,17 @@ void ASTContext::eraseDeclAttrs(const Decl *D) {
}
}
+ArrayRef<CXXDefaultArgExpr *>
+ASTContext::getCtorClosureDefaultArgs(const CXXConstructorDecl *CD) {
+ return CtorClosureDefaultArgs.lookup(CD);
+}
+
+void ASTContext::setCtorClosureDefaultArgs(const CXXConstructorDecl *CD,
+ ArrayRef<CXXDefaultArgExpr *> Args) {
+ assert(!CtorClosureDefaultArgs.contains(CD));
+ CtorClosureDefaultArgs[CD] = Args;
+}
+
ArrayRef<ExplicitInstantiationDecl *>
ASTContext::getExplicitInstantiationDecls(const NamedDecl *Spec) const {
auto It =
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index ce4ba971a4631..e7e0f75fdc84d 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3133,6 +3133,16 @@ bool CXXConstructorDecl::isSpecializationCopyingObject() const {
return ParamType == ClassTy;
}
+ArrayRef<CXXDefaultArgExpr *>
+CXXConstructorDecl::getCtorClosureDefaultArgs() const {
+ return getASTContext().getCtorClosureDefaultArgs(getCanonicalDecl());
+}
+
+void CXXConstructorDecl::setCtorClosureDefaultArgs(
+ ArrayRef<CXXDefaultArgExpr *> Args) {
+ getASTContext().setCtorClosureDefaultArgs(getCanonicalDecl(), Args);
+}
+
void CXXDestructorDecl::anchor() {}
CXXDestructorDecl *CXXDestructorDecl::CreateDeserialized(ASTContext &C,
diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 40c7c00d85395..d324580036f6a 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -4207,18 +4207,17 @@ MicrosoftCXXABI::getAddrOfCXXCtorClosure(const CXXConstructorDecl *CD,
if (SrcVal)
Args.add(RValue::get(SrcVal), SrcParam->getType());
- // Add the rest of the default arguments.
+ // Get the rest of the default arguments.
SmallVector<const Stmt *, 4> ArgVec;
- ArrayRef<ParmVarDecl *> params = CD->parameters().drop_front(IsCopy ? 1 : 0);
- for (const ParmVarDecl *PD : params) {
- assert(PD->hasDefaultArg() && "ctor closure lacks default args");
- ArgVec.push_back(PD->getDefaultArg());
- }
+ for (const CXXDefaultArgExpr *Expr :
+ CD->getCtorClosureDefaultArgs().drop_front(IsCopy ? 1 : 0))
+ ArgVec.push_back(Expr);
+ assert(ArgVec.size() == CD->getNumParams() - IsCopy);
CodeGenFunction::RunCleanupsScope Cleanups(CGF);
const auto *FPT = CD->getType()->castAs<FunctionProtoType>();
- CGF.EmitCallArgs(Args, FPT, llvm::ArrayRef(ArgVec), CD, IsCopy ? 1 : 0);
+ CGF.EmitCallArgs(Args, FPT, ArrayRef(ArgVec), CD, IsCopy ? 1 : 0);
// Insert any ABI-specific implicit constructor arguments.
AddedStructorArgCounts ExtraArgs =
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index f6d1d7a7877c7..9948a633d7e98 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16306,7 +16306,8 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
Context.getTargetInfo().getCXXABI().isMicrosoft()) {
// If this is an MS ABI dllexport default constructor, instantiate any
// default arguments.
- InstantiateDefaultCtorDefaultArgs(Ctor);
+ if (DLLExportAttr *Attr = Ctor->getAttr<DLLExportAttr>())
+ BuildCtorClosureDefaultArgs(Attr->getLocation(), Ctor);
}
}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c645d96da5c00..c1caf9a58d650 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -6339,7 +6339,8 @@ static void ReferenceDllExportedMembers(Sema &S, CXXRecordDecl *Class) {
if (S.Context.getTargetInfo().getCXXABI().isMicrosoft()) {
auto *CD = dyn_cast<CXXConstructorDecl>(MD);
if (CD && CD->isDefaultConstructor() && TSK == TSK_Undeclared) {
- S.InstantiateDefaultCtorDefaultArgs(CD);
+ S.BuildCtorClosureDefaultArgs(
+ CD->getAttr<DLLExportAttr>()->getLocation(), CD);
}
}
@@ -6392,10 +6393,8 @@ static void checkForMultipleExportedDefaultConstructors(Sema &S,
// If the class is non-dependent, mark the default arguments as ODR-used so
// that we can properly codegen the constructor closure.
if (!Class->isDependentContext()) {
- for (ParmVarDecl *PD : CD->parameters()) {
- (void)S.CheckCXXDefaultArgExpr(Attr->getLocation(), CD, PD);
- S.DiscardCleanupsInEvaluationContext();
- }
+ S.BuildCtorClosureDefaultArgs(Attr->getLocation(), CD);
+ S.DiscardCleanupsInEvaluationContext();
}
if (LastExportedDefaultCtor) {
@@ -19817,3 +19816,36 @@ void Sema::ActOnFinishFunctionDeclarationDeclarator(Declarator &Declarator) {
}
InventedParameterInfos.pop_back();
}
+
+bool Sema::BuildCtorClosureDefaultArgs(SourceLocation Loc,
+ CXXConstructorDecl *Ctor, bool IsCopy) {
+ assert(Context.getTargetInfo().getCXXABI().isMicrosoft());
+
+ if (!Ctor->getCtorClosureDefaultArgs().empty()) {
+ // If we build args for default constructor closures, those will have
+ // been generated *before* building args for any copy constructor closures.
+ assert(IsCopy || Ctor->getCtorClosureDefaultArgs()[0] != nullptr);
+ return false;
+ }
+
+ unsigned NumParams = Ctor->getNumParams();
+ if (NumParams == 0)
+ return false;
+
+ CXXDefaultArgExpr **Args =
+ new (getASTContext()) CXXDefaultArgExpr *[NumParams];
+
+ if (IsCopy)
+ Args[0] = nullptr; // Copy ctor closure will provide the first argument.
+
+ for (unsigned I = IsCopy ? 1 : 0; I != NumParams; ++I) {
+ ExprResult R = BuildCXXDefaultArgExpr(Loc, Ctor, Ctor->getParamDecl(I));
+ CleanupVarDeclMarking();
+ if (R.isInvalid())
+ return true;
+ Args[I] = cast<CXXDefaultArgExpr>(R.get());
+ }
+
+ Ctor->setCtorClosureDefaultArgs(ArrayRef(Args, NumParams));
+ return false;
+}
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index ea9e0b945081b..dc7f874927a8e 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -1070,10 +1070,8 @@ bool Sema::CheckCXXThrowOperand(SourceLocation ThrowLoc,
// We don't keep the instantiated default argument expressions around so
// we must rebuild them here.
- for (unsigned I = 1, E = CD->getNumParams(); I != E; ++I) {
- if (CheckCXXDefaultArgExpr(ThrowLoc, CD, CD->getParamDecl(I)))
- return true;
- }
+ if (BuildCtorClosureDefaultArgs(ThrowLoc, CD, /*IsCopy=*/true))
+ return true;
}
}
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 37ef4acd5d3d9..c56203f10ac3c 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -1078,22 +1078,6 @@ void Sema::updateAttrsForLateParsedTemplate(const Decl *Pattern, Decl *Inst) {
}
}
-void Sema::InstantiateDefaultCtorDefaultArgs(CXXConstructorDecl *Ctor) {
- assert(Context.getTargetInfo().getCXXABI().isMicrosoft() &&
- Ctor->isDefaultConstructor());
- unsigned NumParams = Ctor->getNumParams();
- if (NumParams == 0)
- return;
- DLLExportAttr *Attr = Ctor->getAttr<DLLExportAttr>();
- if (!Attr)
- return;
- for (unsigned I = 0; I != NumParams; ++I) {
- (void)CheckCXXDefaultArgExpr(Attr->getLocation(), Ctor,
- Ctor->getParamDecl(I));
- CleanupVarDeclMarking();
- }
-}
-
/// Get the previous declaration of a declaration for the purposes of template
/// instantiation. If this finds a previous declaration, then the previous
/// declaration of the instantiation of D should be an instantiation of the
@@ -5970,7 +5954,8 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
// default arguments.
if (Context.getTargetInfo().getCXXABI().isMicrosoft() &&
Ctor->isDefaultConstructor()) {
- InstantiateDefaultCtorDefaultArgs(Ctor);
+ if (DLLExportAttr *Attr = Ctor->getAttr<DLLExportAttr>())
+ BuildCtorClosureDefaultArgs(Attr->getLocation(), Ctor);
}
}
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index fb291a4b0f2c5..a25e58e434bf2 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2334,6 +2334,14 @@ void ASTDeclReader::VisitCXXConstructorDecl(CXXConstructorDecl *D) {
InheritedConstructor(Shadow, Ctor);
}
+ if (unsigned NumArgs = Record.readUInt32()) {
+ CXXDefaultArgExpr **Args =
+ new (Reader.getContext()) CXXDefaultArgExpr *[NumArgs];
+ for (unsigned I = 0; I != NumArgs; I++)
+ Args[I] = cast<CXXDefaultArgExpr>(Record.readStmt());
+ D->setCtorClosureDefaultArgs(ArrayRef(Args, NumArgs));
+ }
+
VisitCXXMethodDecl(D);
}
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 7f5005aa666c7..f271769d8edf6 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1774,6 +1774,12 @@ void ASTDeclWriter::VisitCXXConstructorDecl(CXXConstructorDecl *D) {
Record.AddDeclRef(Inherited.getConstructor());
}
+ ArrayRef<CXXDefaultArgExpr *> CtorClosureDefaultArgs =
+ D->getCtorClosureDefaultArgs();
+ Record.push_back((unsigned)CtorClosureDefaultArgs.size());
+ for (CXXDefaultArgExpr *Arg : CtorClosureDefaultArgs)
+ Record.writeStmtRef(Arg);
+
VisitCXXMethodDecl(D);
Code = serialization::DECL_CXX_CONSTRUCTOR;
}
diff --git a/clang/test/CodeGenCXX/dllexport-ctor-closure.cpp b/clang/test/CodeGenCXX/dllexport-ctor-closure.cpp
index 8aeb9b9176fcd..1b4940673f599 100644
--- a/clang/test/CodeGenCXX/dllexport-ctor-closure.cpp
+++ b/clang/test/CodeGenCXX/dllexport-ctor-closure.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple i686-windows-msvc -emit-llvm -std=c++14 \
+// RUN: %clang_cc1 -triple i686-windows-msvc -emit-llvm -std=c++20 \
// RUN: -fno-threadsafe-statics -fms-extensions -O1 -mconstructor-aliases \
// RUN: -disable-llvm-passes -o - %s -w -fms-compatibility-version=19.00 | \
// RUN: FileCheck %s
@@ -95,3 +95,10 @@ struct __declspec(dllexport) ConstexprDefaultArg {
ConstexprDefaultArg(SomeStruct = kConstexprStruct) {}
};
// CHECK-LABEL: define weak_odr dso_local dllexport x86_thiscallcc void @"??_FConstexprDefaultArg@@QAEXXZ"
+
+consteval int constEvalFunc() { return 42; }
+struct ConstEvalDefaultArg {
+ __declspec(dllexport) ConstEvalDefaultArg(int n = constEvalFunc()) {}
+};
+// CHECK-LABEL: define weak_odr dso_local dllexport x86_thiscallcc void @"??_FConstEvalDefaultArg@@QAEXXZ"
+// CHECK: call {{.*}} @"??0ConstEvalDefaultArg@@QAE at H@Z"({{.*}}, i32 noundef 42)
diff --git a/clang/test/CodeGenCXX/microsoft-abi-throw.cpp b/clang/test/CodeGenCXX/microsoft-abi-throw.cpp
index 14945ad60f3c1..b37e2c2deec9d 100644
--- a/clang/test/CodeGenCXX/microsoft-abi-throw.cpp
+++ b/clang/test/CodeGenCXX/microsoft-abi-throw.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -emit-llvm -o - -triple=i386-pc-win32 -std=c++11 %s -fcxx-exceptions -fms-extensions | FileCheck %s
+// RUN: %clang_cc1 -emit-llvm -o - -triple=i386-pc-win32 -std=c++20 %s -fcxx-exceptions -fms-extensions | FileCheck %s
// CHECK-DAG: @"??_R0?AUY@@@8" = linkonce_odr global %rtti.TypeDescriptor7 { ptr @"??_7type_info@@6B@", ptr null, [8 x i8] c".?AUY@@\00" }, comdat
// CHECK-DAG: @"_CT??_R0?AUY@@@8??0Y@@QAE at ABU0@@Z8" = linkonce_odr unnamed_addr constant %eh.CatchableType { i32 4, ptr @"??_R0?AUY@@@8", i32 0, i32 -1, i32 0, i32 8, ptr @"??0Y@@QAE at ABU0@@Z" }, section ".xdata", comdat
@@ -71,6 +71,32 @@ void h(Default &d) {
throw d;
}
+consteval int constEvalFunc() { return 123; }
+struct DefaultConstEval {
+ DefaultConstEval(DefaultConstEval&, int = constEvalFunc());
+};
+// CHECK-LABEL: @"??_ODefaultConstEval@@QAEXAAU0@@Z"
+// CHECK: call x86_thiscallcc {{.*}} @"??0DefaultConstEval@@QAE at AAU0@H at Z"({{.*}} %[[this]], {{.*}} %[[src]], i32 noundef 123)
+
+void h2(DefaultConstEval &d) {
+ throw d;
+}
+
+// This will generate both the default constructor closure and the copy constructor closure -- for the same constructor.
+struct DefaultCtorIsCopyCtor;
+const DefaultCtorIsCopyCtor& foo();
+struct DefaultCtorIsCopyCtor {
+ __declspec(dllexport) DefaultCtorIsCopyCtor(const DefaultCtorIsCopyCtor& = foo(), int = 456) {}
+};
+void h3(DefaultCtorIsCopyCtor &d) {
+ throw d;
+}
+// CHECK-LABEL: @"??_FDefaultCtorIsCopyCtor@@QAEXXZ"
+// CHECK: %[[foo:.*]] = call {{.*}} @"?foo@@YAABUDefaultCtorIsCopyCtor@@XZ"
+// CHECK: call {{.*}} @"??0DefaultCtorIsCopyCtor@@QAE at ABU0@H at Z"({{.*}} %[[foo]], i32 noundef 456)
+// CHECK-LABEL: @"??_ODefaultCtorIsCopyCtor@@QAEXABU0@@Z"
+// CHECK: call {{.*}} @"??0DefaultCtorIsCopyCtor@@QAE at ABU0@H at Z"({{.*}} i32 noundef 456)
+
struct DeletedCopy {
DeletedCopy();
DeletedCopy(DeletedCopy &&);
diff --git a/clang/test/SemaCXX/ms-ctor-closure.cpp b/clang/test/SemaCXX/ms-ctor-closure.cpp
new file mode 100644
index 0000000000000..05aa5d40b4427
--- /dev/null
+++ b/clang/test/SemaCXX/ms-ctor-closure.cpp
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 %s -triple=i386-pc-win32 -std=c++23 -fms-extensions -verify
+
+consteval int bad(int x) { return 42 / x; } // expected-note{{division by zero}}
+
+struct ExportedDefaultArgClosure {
+ __declspec(dllexport) // expected-note{{in the default initializer of 'x'}}
+ ExportedDefaultArgClosure(int x // expected-note{{declared here}}
+ = bad(0)) { // expected-error{{call to consteval function 'bad' is not a constant expression}} expected-note{{in call to 'bad(0)'}}
+ }
+};
More information about the cfe-commits
mailing list