https://github.com/chinmaydd updated https://github.com/llvm/llvm-project/pull/182706
>From 457567bdc05916579c2c8c60f0596808fdaf6aec Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Fri, 20 Feb 2026 16:20:05 -0800 Subject: [PATCH 1/9] [Clang] Ensure child classes export inherited constructors from dllexport-ed base classes --- clang/lib/AST/ASTContext.cpp | 5 +- clang/lib/Sema/SemaDeclCXX.cpp | 30 ++++++++++ .../CodeGenCXX/dllexport-inherited-ctor.cpp | 58 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index d650555d10f11..c7287791582c0 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13013,10 +13013,13 @@ static GVALinkage basicGVALinkageForFunction(const ASTContext &Context, if (Context.getTargetInfo().getCXXABI().isMicrosoft() && isa<CXXConstructorDecl>(FD) && - cast<CXXConstructorDecl>(FD)->isInheritingConstructor()) + cast<CXXConstructorDecl>(FD)->isInheritingConstructor() && + !FD->hasAttr<DLLExportAttr>()) // Our approach to inheriting constructors is fundamentally different from // that used by the MS ABI, so keep our inheriting constructor thunks // internal rather than trying to pick an unambiguous mangling for them. + // However, dllexport inherited constructors must be externally visible + // to match MSVC's behavior. return GVA_Internal; return GVA_DiscardableODR; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 5837ecd6b9163..34f9e159b4355 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6312,6 +6312,12 @@ static void ReferenceDllExportedMembers(Sema &S, CXXRecordDecl *Class) { S.MarkFunctionReferenced(Class->getLocation(), MD); + // Inherited constructors are synthesized, not written in source, so + // their definitions won't be encountered later. + if (auto *CD = dyn_cast<CXXConstructorDecl>(MD)) + if (CD->getInheritedConstructor()) + S.Consumer.HandleTopLevelDecl(DeclGroupRef(MD)); + // The function will be passed to the consumer when its definition is // encountered. } else if (MD->isExplicitlyDefaulted()) { @@ -6588,6 +6594,19 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl *Class) { // Force declaration of implicit members so they can inherit the attribute. ForceDeclarationOfImplicitMembers(Class); + // Inherited constructors are created lazily; force their creation now so the + // loop below can propagate the DLL attribute to them. + if (ClassExported) { + SmallVector<ConstructorUsingShadowDecl *, 4> Shadows; + for (auto *D : Class->decls()) + if (auto *S = dyn_cast<ConstructorUsingShadowDecl>(D)) + Shadows.push_back(S); + for (auto *S : Shadows) + if (auto *BC = dyn_cast<CXXConstructorDecl>(S->getTargetDecl()); + BC && !BC->isDeleted()) + findInheritingConstructor(Class->getLocation(), BC, S); + } + // FIXME: MSVC's docs say all bases must be exportable, but this doesn't // seem to be true in practice? @@ -14367,6 +14386,17 @@ Sema::findInheritingConstructor(SourceLocation Loc, DerivedCtor->setParams(ParamDecls); Derived->addDecl(DerivedCtor); + // Propagate the class-level DLLExport attribute to the new inherited + // constructor so it gets exported. DLLImport is not propagated because the + // inherited ctor is an inline definition synthesized by the compiler. + if (Derived->hasAttr<DLLExportAttr>() && + !DerivedCtor->hasAttr<DLLExportAttr>()) { + auto *NewAttr = ::new (getASTContext()) + DLLExportAttr(getASTContext(), *Derived->getAttr<DLLExportAttr>()); + NewAttr->setInherited(true); + DerivedCtor->addAttr(NewAttr); + } + if (ShouldDeleteSpecialMember(DerivedCtor, CXXSpecialMemberKind::DefaultConstructor, &ICI)) SetDeclDeleted(DerivedCtor, UsingLoc); diff --git a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp new file mode 100644 index 0000000000000..6317b18886f2d --- /dev/null +++ b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp @@ -0,0 +1,58 @@ +// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-msvc -emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck --check-prefix=MSVC %s +// RUN: %clang_cc1 -no-enable-noundef-analysis -triple i686-windows-msvc -emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck --check-prefix=M32 %s +// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-gnu -emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck --check-prefix=GNU %s + +// Test that inherited constructors via 'using Base::Base' in a dllexport +// class are properly exported (https://github.com/llvm/llvm-project/issues/162640). + +struct __declspec(dllexport) Base { + Base(int); + Base(double); +}; + +struct __declspec(dllexport) Child : public Base { + using Base::Base; +}; + +// The inherited constructors Child(int) and Child(double) should be exported. + +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QEAA@H@Z" +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QEAA@N@Z" + +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QAE@H@Z" +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QAE@N@Z" + +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN5ChildCI14BaseEi( +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN5ChildCI14BaseEd( + +// Also test that a non-exported derived class does not export inherited ctors. +struct NonExportedBase { + NonExportedBase(int); +}; + +struct NonExportedChild : public NonExportedBase { + using NonExportedBase::NonExportedBase; +}; + +// MSVC-NOT: dllexport{{.*}}NonExportedChild +// M32-NOT: dllexport{{.*}}NonExportedChild +// GNU-NOT: dllexport{{.*}}NonExportedChild + +// Test that only the derived class is dllexport, base is not. +struct PlainBase { + PlainBase(int); + PlainBase(float); +}; + +struct __declspec(dllexport) ExportedChild : public PlainBase { + using PlainBase::PlainBase; +}; + +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0ExportedChild@@QEAA@H@Z" +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0ExportedChild@@QEAA@M@Z" + +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0ExportedChild@@QAE@H@Z" +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0ExportedChild@@QAE@M@Z" + +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN13ExportedChildCI19PlainBaseEi( +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN13ExportedChildCI19PlainBaseEf( >From bd03909024bcc789d01d8622dd7827a983a025d3 Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Tue, 24 Feb 2026 12:49:43 -0500 Subject: [PATCH 2/9] [Clang] Incorporate comments Change-Id: I5ed2513f80010e4bf9e5a0687ea32248c74dcaa4 --- clang/lib/Sema/SemaDeclCXX.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 34f9e159b4355..85be44ac15651 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6598,10 +6598,10 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl *Class) { // loop below can propagate the DLL attribute to them. if (ClassExported) { SmallVector<ConstructorUsingShadowDecl *, 4> Shadows; - for (auto *D : Class->decls()) + for (Decl *D : Class->decls()) if (auto *S = dyn_cast<ConstructorUsingShadowDecl>(D)) Shadows.push_back(S); - for (auto *S : Shadows) + for (ConstructorUsingShadowDecl *S : Shadows) if (auto *BC = dyn_cast<CXXConstructorDecl>(S->getTargetDecl()); BC && !BC->isDeleted()) findInheritingConstructor(Class->getLocation(), BC, S); @@ -14391,8 +14391,8 @@ Sema::findInheritingConstructor(SourceLocation Loc, // inherited ctor is an inline definition synthesized by the compiler. if (Derived->hasAttr<DLLExportAttr>() && !DerivedCtor->hasAttr<DLLExportAttr>()) { - auto *NewAttr = ::new (getASTContext()) - DLLExportAttr(getASTContext(), *Derived->getAttr<DLLExportAttr>()); + auto *NewAttr = DLLExportAttr::CreateImplicit( + getASTContext(), *Derived->getAttr<DLLExportAttr>()); NewAttr->setInherited(true); DerivedCtor->addAttr(NewAttr); } >From 7c326e1af831cf5d85854d4e0c54e0325a9f3fe5 Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Tue, 24 Feb 2026 16:31:48 -0800 Subject: [PATCH 3/9] [Clang] Incorporate comments --- clang/lib/AST/ASTContext.cpp | 7 ++++++- clang/lib/CodeGen/ModuleBuilder.cpp | 16 ++++++++++++++++ clang/lib/Sema/SemaDeclCXX.cpp | 17 ----------------- .../CodeGenCXX/dllexport-inherited-ctor.cpp | 8 ++++++-- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index c7287791582c0..394064edf4bf3 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13018,8 +13018,13 @@ static GVALinkage basicGVALinkageForFunction(const ASTContext &Context, // Our approach to inheriting constructors is fundamentally different from // that used by the MS ABI, so keep our inheriting constructor thunks // internal rather than trying to pick an unambiguous mangling for them. + // // However, dllexport inherited constructors must be externally visible - // to match MSVC's behavior. + // to match MSVC's behavior. This is ABI-compatible because the MS ABI + // mangles inherited constructors identically to regular constructors + // (only class name + parameter types, no "inherited from" encoding), + // and Clang's forwarding thunks produce an equivalent calling sequence + // for the common case (no virtual bases). return GVA_Internal; return GVA_DiscardableODR; diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp b/clang/lib/CodeGen/ModuleBuilder.cpp index b4885d572c294..668e9ed2a0466 100644 --- a/clang/lib/CodeGen/ModuleBuilder.cpp +++ b/clang/lib/CodeGen/ModuleBuilder.cpp @@ -260,6 +260,22 @@ namespace { } } } + + // Emit dllexport inherited constructors. These are synthesized during + // Sema (in checkClassLevelDLLAttribute) but have no written definition, + // so they must be emitted now while visiting the class definition. + if (auto *RD = dyn_cast<CXXRecordDecl>(D)) { + if (RD->hasAttr<DLLExportAttr>()) { + for (Decl *Member : RD->decls()) { + if (auto *CD = dyn_cast<CXXConstructorDecl>(Member)) { + if (CD->getInheritedConstructor() && + CD->hasAttr<DLLExportAttr>() && !CD->isDeleted()) + Builder->EmitTopLevelDecl(CD); + } + } + } + } + // For OpenMP emit declare reduction functions, if required. if (Ctx->getLangOpts().OpenMP) { for (Decl *Member : D->decls()) { diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 85be44ac15651..e0c19cb073ab7 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6312,12 +6312,6 @@ static void ReferenceDllExportedMembers(Sema &S, CXXRecordDecl *Class) { S.MarkFunctionReferenced(Class->getLocation(), MD); - // Inherited constructors are synthesized, not written in source, so - // their definitions won't be encountered later. - if (auto *CD = dyn_cast<CXXConstructorDecl>(MD)) - if (CD->getInheritedConstructor()) - S.Consumer.HandleTopLevelDecl(DeclGroupRef(MD)); - // The function will be passed to the consumer when its definition is // encountered. } else if (MD->isExplicitlyDefaulted()) { @@ -14386,17 +14380,6 @@ Sema::findInheritingConstructor(SourceLocation Loc, DerivedCtor->setParams(ParamDecls); Derived->addDecl(DerivedCtor); - // Propagate the class-level DLLExport attribute to the new inherited - // constructor so it gets exported. DLLImport is not propagated because the - // inherited ctor is an inline definition synthesized by the compiler. - if (Derived->hasAttr<DLLExportAttr>() && - !DerivedCtor->hasAttr<DLLExportAttr>()) { - auto *NewAttr = DLLExportAttr::CreateImplicit( - getASTContext(), *Derived->getAttr<DLLExportAttr>()); - NewAttr->setInherited(true); - DerivedCtor->addAttr(NewAttr); - } - if (ShouldDeleteSpecialMember(DerivedCtor, CXXSpecialMemberKind::DefaultConstructor, &ICI)) SetDeclDeleted(DerivedCtor, UsingLoc); diff --git a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp index 6317b18886f2d..299e2be42c8c5 100644 --- a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp +++ b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp @@ -15,9 +15,13 @@ struct __declspec(dllexport) Child : public Base { }; // The inherited constructors Child(int) and Child(double) should be exported. +// Verify the thunk bodies delegate to the base constructor with the correct +// arguments, ensuring ABI compatibility with MSVC-compiled callers. -// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QEAA@H@Z" -// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QEAA@N@Z" +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QEAA@H@Z"(ptr {{.*}} %this, i32 %0) +// MSVC-DAG: call {{.*}} @"??0Base@@QEAA@H@Z"(ptr {{.*}} %this{{.*}}, i32 +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QEAA@N@Z"(ptr {{.*}} %this, double %0) +// MSVC-DAG: call {{.*}} @"??0Base@@QEAA@N@Z"(ptr {{.*}} %this{{.*}}, double // M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QAE@H@Z" // M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QAE@N@Z" >From f084baa724b0df714fc48c528f8d8be1879b154a Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Tue, 24 Feb 2026 17:01:01 -0800 Subject: [PATCH 4/9] [Clang] fix tests --- clang/lib/Sema/SemaDeclCXX.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index e0c19cb073ab7..d03a361e1e83e 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6619,11 +6619,15 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl *Class) { if (MD->isInlined()) { // MinGW does not import or export inline methods. But do it for - // template instantiations. + // template instantiations and inherited constructors (which are + // marked inline but must be exported to match MSVC behavior). if (!Context.getTargetInfo().shouldDLLImportComdatSymbols() && TSK != TSK_ExplicitInstantiationDeclaration && - TSK != TSK_ExplicitInstantiationDefinition) - continue; + TSK != TSK_ExplicitInstantiationDefinition) { + if (auto *CD = dyn_cast<CXXConstructorDecl>(MD); + !CD || !CD->getInheritedConstructor()) + continue; + } // MSVC versions before 2015 don't export the move assignment operators // and move constructor, so don't attempt to import/export them if >From b71a238b570e1946ed95e4ef556ff34393773677 Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Wed, 25 Feb 2026 12:39:55 -0800 Subject: [PATCH 5/9] [Clang] Update comment --- clang/lib/AST/ASTContext.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 394064edf4bf3..5c209f23855dd 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13015,16 +13015,13 @@ static GVALinkage basicGVALinkageForFunction(const ASTContext &Context, isa<CXXConstructorDecl>(FD) && cast<CXXConstructorDecl>(FD)->isInheritingConstructor() && !FD->hasAttr<DLLExportAttr>()) - // Our approach to inheriting constructors is fundamentally different from - // that used by the MS ABI, so keep our inheriting constructor thunks - // internal rather than trying to pick an unambiguous mangling for them. + // Both Clang and MSVC implement inherited constructors as forwarding + // thunks that delegate to the base constructor with the same mangled + // names and calling convention. Keep non-dllexport inheriting constructor + // thunks internal since they are not needed outside the translation unit. // - // However, dllexport inherited constructors must be externally visible - // to match MSVC's behavior. This is ABI-compatible because the MS ABI - // mangles inherited constructors identically to regular constructors - // (only class name + parameter types, no "inherited from" encoding), - // and Clang's forwarding thunks produce an equivalent calling sequence - // for the common case (no virtual bases). + // But, dllexport inherited constructors must be externally visible to + // match MSVC's behavior. return GVA_Internal; return GVA_DiscardableODR; >From fee0952197bb528faa6a0c937af4060672867120 Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Wed, 25 Feb 2026 13:11:43 -0800 Subject: [PATCH 6/9] Apply suggestion from @chinmaydd --- clang/lib/AST/ASTContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 5c209f23855dd..f5109e133c81b 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13020,7 +13020,7 @@ static GVALinkage basicGVALinkageForFunction(const ASTContext &Context, // names and calling convention. Keep non-dllexport inheriting constructor // thunks internal since they are not needed outside the translation unit. // - // But, dllexport inherited constructors must be externally visible to + // But, dllexport inherited constructors must be externally visible to // match MSVC's behavior. return GVA_Internal; >From 2132a4c955e12750776a35003e8a1af412fed3bf Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Thu, 26 Feb 2026 15:41:47 -0800 Subject: [PATCH 7/9] [Clang] Make change for corner cases, update tests --- clang/lib/AST/ASTContext.cpp | 10 +- clang/lib/Sema/SemaDeclCXX.cpp | 8 +- .../CodeGenCXX/dllexport-inherited-ctor.cpp | 140 +++++++++++++++++- 3 files changed, 150 insertions(+), 8 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index f5109e133c81b..428ce24fcf016 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13015,13 +13015,13 @@ static GVALinkage basicGVALinkageForFunction(const ASTContext &Context, isa<CXXConstructorDecl>(FD) && cast<CXXConstructorDecl>(FD)->isInheritingConstructor() && !FD->hasAttr<DLLExportAttr>()) - // Both Clang and MSVC implement inherited constructors as forwarding - // thunks that delegate to the base constructor with the same mangled - // names and calling convention. Keep non-dllexport inheriting constructor + // Clang's inheriting constructor thunks use a forwarding model that + // differs from MSVC's implementation in some edge cases (e.g. inalloca + // argument forwarding on i686). Keep non-dllexport inheriting constructor // thunks internal since they are not needed outside the translation unit. // - // But, dllexport inherited constructors must be externally visible to - // match MSVC's behavior. + // dllexport inherited constructors are exempted so they are externally + // visible, matching MSVC's export behavior for the common cases. return GVA_Internal; return GVA_DiscardableODR; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index d03a361e1e83e..530b96019b782 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6660,8 +6660,14 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl *Class) { // Do not export/import inline function when -fno-dllexport-inlines is // passed. But add attribute for later local static var check. + // Inherited constructors are marked inline but must still be exported + // to match MSVC behavior, so exclude them from this override. + bool IsInheritedCtor = false; + if (MD) + if (auto *CD = dyn_cast<CXXConstructorDecl>(MD)) + IsInheritedCtor = (bool)CD->getInheritedConstructor(); if (!getLangOpts().DllExportInlines && MD && MD->isInlined() && - TSK != TSK_ExplicitInstantiationDeclaration && + !IsInheritedCtor && TSK != TSK_ExplicitInstantiationDeclaration && TSK != TSK_ExplicitInstantiationDefinition) { if (ClassExported) { NewAttr = ::new (getASTContext()) diff --git a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp index 299e2be42c8c5..06feae7eb42eb 100644 --- a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp +++ b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp @@ -1,10 +1,15 @@ // RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-msvc -emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck --check-prefix=MSVC %s // RUN: %clang_cc1 -no-enable-noundef-analysis -triple i686-windows-msvc -emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck --check-prefix=M32 %s // RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-gnu -emit-llvm -std=c++17 -fms-extensions -O0 -o - %s | FileCheck --check-prefix=GNU %s +// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-windows-msvc -emit-llvm -std=c++17 -fms-extensions -fno-dllexport-inlines -O0 -o - %s | FileCheck --check-prefix=NOINLINE %s // Test that inherited constructors via 'using Base::Base' in a dllexport // class are properly exported (https://github.com/llvm/llvm-project/issues/162640). +//===----------------------------------------------------------------------===// +// Basic: both base and child exported, simple parameter types. +//===----------------------------------------------------------------------===// + struct __declspec(dllexport) Base { Base(int); Base(double); @@ -29,7 +34,10 @@ struct __declspec(dllexport) Child : public Base { // GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN5ChildCI14BaseEi( // GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN5ChildCI14BaseEd( -// Also test that a non-exported derived class does not export inherited ctors. +//===----------------------------------------------------------------------===// +// Non-exported class should not export inherited ctors. +//===----------------------------------------------------------------------===// + struct NonExportedBase { NonExportedBase(int); }; @@ -42,7 +50,10 @@ struct NonExportedChild : public NonExportedBase { // M32-NOT: dllexport{{.*}}NonExportedChild // GNU-NOT: dllexport{{.*}}NonExportedChild -// Test that only the derived class is dllexport, base is not. +//===----------------------------------------------------------------------===// +// Only the derived class is dllexport, base is not. +//===----------------------------------------------------------------------===// + struct PlainBase { PlainBase(int); PlainBase(float); @@ -60,3 +71,128 @@ struct __declspec(dllexport) ExportedChild : public PlainBase { // GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN13ExportedChildCI19PlainBaseEi( // GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN13ExportedChildCI19PlainBaseEf( + +//===----------------------------------------------------------------------===// +// Multi-level inheritance: A -> B -> C with using at each level. +//===----------------------------------------------------------------------===// + +struct MLBase { + MLBase(int); +}; + +struct MLMiddle : MLBase { + using MLBase::MLBase; +}; + +struct __declspec(dllexport) MLChild : MLMiddle { + using MLMiddle::MLMiddle; +}; + +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0MLChild@@QEAA@H@Z" +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0MLChild@@QAE@H@Z" +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN7MLChildCI16MLBaseEi( + +//===----------------------------------------------------------------------===// +// Class template specialization with inherited constructors. +//===----------------------------------------------------------------------===// + +template <typename T> +struct TplBase { + TplBase(T); +}; + +struct __declspec(dllexport) TplChild : TplBase<int> { + using TplBase<int>::TplBase; +}; + +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0TplChild@@QEAA@H@Z" +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0TplChild@@QAE@H@Z" +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN8TplChildCI17TplBaseIiEEi( + +//===----------------------------------------------------------------------===// +// Default arguments: thunk takes the full parameter list. +//===----------------------------------------------------------------------===// + +struct DefArgBase { + DefArgBase(int a, int b = 10, int c = 20); +}; + +struct __declspec(dllexport) DefArgChild : DefArgBase { + using DefArgBase::DefArgBase; +}; + +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0DefArgChild@@QEAA@HHH@Z"(ptr {{.*}} %this, i32 %0, i32 %1, i32 %2) +// MSVC-DAG: call {{.*}} @"??0DefArgBase@@QEAA@HHH@Z"(ptr {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 + +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0DefArgChild@@QAE@HHH@Z" +// M32-DAG: call {{.*}} @"??0DefArgBase@@QAE@HHH@Z"(ptr {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 + +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN11DefArgChildCI110DefArgBaseEiii( +// GNU-DAG: call {{.*}} @_ZN10DefArgBaseC2Eiii(ptr {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 + +//===----------------------------------------------------------------------===// +// Default arguments with mixed types. +//===----------------------------------------------------------------------===// + +struct MixedDefBase { + MixedDefBase(int a, double b = 3.14); +}; + +struct __declspec(dllexport) MixedDefChild : MixedDefBase { + using MixedDefBase::MixedDefBase; +}; + +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0MixedDefChild@@QEAA@HN@Z"(ptr {{.*}} %this, i32 %0, double %1) +// MSVC-DAG: call {{.*}} @"??0MixedDefBase@@QEAA@HN@Z"(ptr {{.*}}, i32 {{.*}}, double + +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0MixedDefChild@@QAE@HN@Z" +// M32-DAG: call {{.*}} @"??0MixedDefBase@@QAE@HN@Z"(ptr {{.*}}, i32 {{.*}}, double + +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN13MixedDefChildCI112MixedDefBaseEid( +// GNU-DAG: call {{.*}} @_ZN12MixedDefBaseC2Eid(ptr {{.*}}, i32 {{.*}}, double + +//===----------------------------------------------------------------------===// +// All parameters have defaults. The inherited constructor takes the full +// parameter list. The implicit default constructor also gets exported, with +// default argument values baked in. +//===----------------------------------------------------------------------===// + +struct AllDefBase { + AllDefBase(int a = 1, int b = 2); +}; + +struct __declspec(dllexport) AllDefChild : AllDefBase { + using AllDefBase::AllDefBase; +}; + +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0AllDefChild@@QEAA@HH@Z"(ptr {{.*}} %this, i32 %0, i32 %1) +// MSVC-DAG: define weak_odr dso_local dllexport {{.*}} @"??0AllDefChild@@QEAA@XZ"(ptr {{.*}} %this) +// MSVC-DAG: call {{.*}} @"??0AllDefBase@@QEAA@HH@Z"(ptr {{.*}}, i32 1, i32 2) + +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0AllDefChild@@QAE@HH@Z" +// M32-DAG: call {{.*}} @"??0AllDefBase@@QAE@HH@Z"(ptr {{.*}}, i32 % +// M32-DAG: define weak_odr dso_local dllexport {{.*}} @"??0AllDefChild@@QAE@XZ" +// M32-DAG: call {{.*}} @"??0AllDefBase@@QAE@HH@Z"(ptr {{.*}}, i32 1, i32 2) + +// GNU does not emit an implicit default constructor unless it is used. +// Only the inherited constructor with the full parameter list is exported. +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN11AllDefChildCI110AllDefBaseEii( +// GNU-DAG: call {{.*}} @_ZN10AllDefBaseC2Eii(ptr {{.*}}, i32 %{{.*}}, i32 % + +//===----------------------------------------------------------------------===// +// -fno-dllexport-inlines should still export inherited constructors. +// Inherited constructors are marked inline internally but must be exported. +//===----------------------------------------------------------------------===// + +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QEAA@H@Z" +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0Child@@QEAA@N@Z" +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0ExportedChild@@QEAA@H@Z" +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0ExportedChild@@QEAA@M@Z" +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0MLChild@@QEAA@H@Z" +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0TplChild@@QEAA@H@Z" +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0DefArgChild@@QEAA@HHH@Z" +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0MixedDefChild@@QEAA@HN@Z" +// NOINLINE-DAG: define weak_odr dso_local dllexport {{.*}} @"??0AllDefChild@@QEAA@HH@Z" +// The implicit default ctor is a regular inline method, NOT an inherited +// constructor, so -fno-dllexport-inlines correctly suppresses it. +// NOINLINE-NOT: define {{.*}}dllexport{{.*}} @"??0AllDefChild@@QEAA@XZ" >From 9e5d963c9f883176ae42f82d9adcad1339484f7a Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Thu, 26 Feb 2026 19:32:14 -0800 Subject: [PATCH 8/9] [Clang] Filter ABI-incompatible funcs from being exported --- clang/lib/AST/ASTContext.cpp | 13 ++++++---- clang/lib/Sema/SemaDeclCXX.cpp | 25 ++++++++++++++++++ .../CodeGenCXX/dllexport-inherited-ctor.cpp | 26 +++++++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 428ce24fcf016..f1dd9f93e8360 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13015,13 +13015,16 @@ static GVALinkage basicGVALinkageForFunction(const ASTContext &Context, isa<CXXConstructorDecl>(FD) && cast<CXXConstructorDecl>(FD)->isInheritingConstructor() && !FD->hasAttr<DLLExportAttr>()) - // Clang's inheriting constructor thunks use a forwarding model that - // differs from MSVC's implementation in some edge cases (e.g. inalloca - // argument forwarding on i686). Keep non-dllexport inheriting constructor - // thunks internal since they are not needed outside the translation unit. + // Both Clang and MSVC implement inherited constructors as forwarding + // thunks that delegate to the base constructor. Keep non-dllexport + // inheriting constructor thunks internal since they are not needed + // outside the translation unit. // // dllexport inherited constructors are exempted so they are externally - // visible, matching MSVC's export behavior for the common cases. + // visible, matching MSVC's export behavior. Inherited constructors + // whose parameters prevent ABI-compatible forwarding (e.g. callee- + // cleanup types) are excluded from export in Sema to avoid silent + // runtime mismatches. return GVA_Internal; return GVA_DiscardableODR; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 530b96019b782..7bfc4088b324c 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6617,6 +6617,31 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl *Class) { if (MD->isDeleted()) continue; + // Don't export inherited constructors whose parameters prevent ABI- + // compatible forwarding thunk. When canEmitDelegateCallArgs (in + // CodeGen) returns false, Clang inlines the constructor body instead + // of emitting a forwarding thunk, producing code that is not ABI- + // compatible with MSVC. Suppress the export so the user gets a linker + // error rather than a silent runtime mismatch. + if (ClassExported) { + if (auto *CD = dyn_cast<CXXConstructorDecl>(MD)) { + if (CD->getInheritedConstructor()) { + if (CD->isVariadic()) + continue; + if (Context.getTargetInfo() + .getCXXABI() + .areArgsDestroyedLeftToRightInCallee()) { + bool HasCalleeCleanupParam = false; + for (const auto *P : CD->parameters()) + if (P->needsDestruction(Context)) + HasCalleeCleanupParam = true; + if (HasCalleeCleanupParam) + continue; + } + } + } + } + if (MD->isInlined()) { // MinGW does not import or export inline methods. But do it for // template instantiations and inherited constructors (which are diff --git a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp index 06feae7eb42eb..bf64900b43298 100644 --- a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp +++ b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp @@ -179,6 +179,32 @@ struct __declspec(dllexport) AllDefChild : AllDefBase { // GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN11AllDefChildCI110AllDefBaseEii( // GNU-DAG: call {{.*}} @_ZN10AllDefBaseC2Eii(ptr {{.*}}, i32 %{{.*}}, i32 % +//===----------------------------------------------------------------------===// +// Callee-cleanup parameter: struct with non-trivial destructor passed by value. +// canEmitDelegateCallArgs returns false for this case, so the inherited +// constructor must NOT be exported to avoid ABI incompatibility with MSVC. +//===----------------------------------------------------------------------===// + +struct NontrivialDtor { + int x; + ~NontrivialDtor(); +}; + +struct CalleeCleanupBase { + CalleeCleanupBase(NontrivialDtor); +}; + +struct __declspec(dllexport) CalleeCleanupChild : CalleeCleanupBase { + using CalleeCleanupBase::CalleeCleanupBase; +}; + +// The inherited constructor should NOT be exported on MSVC targets because the +// parameter requires callee-cleanup, making the thunk ABI-incompatible. +// On GNU targets the callee-cleanup issue does not apply, so export is fine. +// MSVC-NOT: dllexport{{.*}}CalleeCleanupChild{{.*}}NontrivialDtor +// M32-NOT: dllexport{{.*}}CalleeCleanupChild{{.*}}NontrivialDtor +// GNU-DAG: define {{.*}}dso_local dllexport {{.*}}CalleeCleanupChild{{.*}}NontrivialDtor + //===----------------------------------------------------------------------===// // -fno-dllexport-inlines should still export inherited constructors. // Inherited constructors are marked inline internally but must be exported. >From 2387a2b1b0b422daa16f0327c5f9428fa117276b Mon Sep 17 00:00:00 2001 From: Chinmay Deshpande <[email protected]> Date: Fri, 27 Feb 2026 11:57:08 -0800 Subject: [PATCH 9/9] [Clang] Add warnings --- .../clang/Basic/DiagnosticSemaKinds.td | 5 ++++ clang/lib/Sema/SemaDeclCXX.cpp | 22 ++++++++++------ .../CodeGenCXX/dllexport-inherited-ctor.cpp | 19 ++++++++++++++ clang/test/SemaCXX/dllexport.cpp | 25 +++++++++++++++++++ 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 8a3b9de19ad32..0c378413b20df 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3926,6 +3926,11 @@ def warn_dllexport_on_decl_ignored : Warning< InGroup<IgnoredAttributes>; def note_dllexport_on_decl : Note< "'dllexport' attribute on the declaration is ignored">; +def warn_dllexport_inherited_ctor_unsupported + : Warning<"exporting inherited constructor is not yet supported; " + "'dllexport' ignored on inherited constructor with " + "%select{variadic arguments|callee-cleanup parameters}0">, + InGroup<IgnoredAttributes>; def warn_attribute_exclude_from_explicit_instantiation_local_class : Warning< "%0 attribute ignored on local class%select{| member}1">, InGroup<IgnoredAttributes>; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 7bfc4088b324c..d8707e2f176aa 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6618,16 +6618,20 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl *Class) { continue; // Don't export inherited constructors whose parameters prevent ABI- - // compatible forwarding thunk. When canEmitDelegateCallArgs (in - // CodeGen) returns false, Clang inlines the constructor body instead - // of emitting a forwarding thunk, producing code that is not ABI- - // compatible with MSVC. Suppress the export so the user gets a linker - // error rather than a silent runtime mismatch. + // compatible forwarding. When canEmitDelegateCallArgs (in CodeGen) + // returns false, Clang inlines the constructor body instead of + // emitting a forwarding thunk, producing code that is not ABI- + // compatible with MSVC. Suppress the export and warn so the user + // gets a linker error rather than a silent runtime mismatch. if (ClassExported) { if (auto *CD = dyn_cast<CXXConstructorDecl>(MD)) { if (CD->getInheritedConstructor()) { - if (CD->isVariadic()) + if (CD->isVariadic()) { + Diag(CD->getLocation(), + diag::warn_dllexport_inherited_ctor_unsupported) + << /*variadic=*/0; continue; + } if (Context.getTargetInfo() .getCXXABI() .areArgsDestroyedLeftToRightInCallee()) { @@ -6635,8 +6639,12 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl *Class) { for (const auto *P : CD->parameters()) if (P->needsDestruction(Context)) HasCalleeCleanupParam = true; - if (HasCalleeCleanupParam) + if (HasCalleeCleanupParam) { + Diag(CD->getLocation(), + diag::warn_dllexport_inherited_ctor_unsupported) + << /*callee-cleanup=*/1; continue; + } } } } diff --git a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp index bf64900b43298..cad081fd7f999 100644 --- a/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp +++ b/clang/test/CodeGenCXX/dllexport-inherited-ctor.cpp @@ -179,6 +179,25 @@ struct __declspec(dllexport) AllDefChild : AllDefBase { // GNU-DAG: define {{.*}}dso_local dllexport {{.*}} @_ZN11AllDefChildCI110AllDefBaseEii( // GNU-DAG: call {{.*}} @_ZN10AllDefBaseC2Eii(ptr {{.*}}, i32 %{{.*}}, i32 % +//===----------------------------------------------------------------------===// +// Variadic constructor: inherited variadic constructors cannot be exported +// because delegate forwarding is not supported for variadic arguments. +//===----------------------------------------------------------------------===// + +struct VariadicBase { + VariadicBase(int, ...); +}; + +struct __declspec(dllexport) VariadicChild : VariadicBase { + using VariadicBase::VariadicBase; +}; + +// The variadic inherited constructor (int, ...) should NOT be exported. +// Match specifically to avoid matching implicitly exported copy/move ctors. +// MSVC-NOT: dllexport{{.*}}??0VariadicChild@@QEAA@H +// M32-NOT: dllexport{{.*}}??0VariadicChild@@QAE@H +// GNU-NOT: dllexport{{.*}}VariadicChildCI1{{.*}}Eiz + //===----------------------------------------------------------------------===// // Callee-cleanup parameter: struct with non-trivial destructor passed by value. // canEmitDelegateCallArgs returns false for this case, so the inherited diff --git a/clang/test/SemaCXX/dllexport.cpp b/clang/test/SemaCXX/dllexport.cpp index f5ff99da38091..d625b15557762 100644 --- a/clang/test/SemaCXX/dllexport.cpp +++ b/clang/test/SemaCXX/dllexport.cpp @@ -1131,3 +1131,28 @@ template<typename T> template<typename U> __declspec(dllexport) constexpr int CT //===----------------------------------------------------------------------===// // The MS ABI doesn't provide a stable mangling for lambdas, so they can't be imported or exported. auto Lambda = []() __declspec(dllexport) -> bool { return true; }; // non-gnu-error {{lambda cannot be declared 'dllexport'}} + +//===----------------------------------------------------------------------===// +// Inherited constructors: unsupported export warnings +//===----------------------------------------------------------------------===// + +struct VariadicBase { + VariadicBase(int, ...); +}; + +struct __declspec(dllexport) VariadicChild : VariadicBase { + using VariadicBase::VariadicBase; // expected-warning{{exporting inherited constructor is not yet supported; 'dllexport' ignored on inherited constructor with variadic arguments}} +}; + +struct NontrivialDtorParam { + int x; + ~NontrivialDtorParam(); +}; + +struct CalleeCleanupBase { + CalleeCleanupBase(NontrivialDtorParam); +}; + +struct __declspec(dllexport) CalleeCleanupChild : CalleeCleanupBase { + using CalleeCleanupBase::CalleeCleanupBase; // ms-warning{{exporting inherited constructor is not yet supported; 'dllexport' ignored on inherited constructor with callee-cleanup parameters}} +}; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
