diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -11696,7 +11696,7 @@ const Decl *D, GVALinkage L) { // See http://msdn.microsoft.com/en-us/library/xa0d9ste.aspx // dllexport/dllimport on inline functions. - if (D->hasAttr()) { + if (D->hasAttr() && !D->hasAttr()) { if (L == GVA_DiscardableODR || L == GVA_StrongODR) return GVA_AvailableExternally; } else if (D->hasAttr()) { diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -1512,7 +1512,7 @@ void CodeGenModule::setDLLImportDLLExport(llvm::GlobalValue *GV, const NamedDecl *D) const { - if (D && D->isExternallyVisible()) { + if (D && D->isExternallyVisible() && !D->hasAttr()) { if (D->hasAttr()) GV->setDLLStorageClass(llvm::GlobalVariable::DLLImportStorageClass); else if ((D->hasAttr() || diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -818,10 +818,13 @@ if (isa(ND)) continue; - if (ND->hasAttr() || ND->hasAttr()) { + if ((ND->hasAttr() || ND->hasAttr()) && + !ND->hasAttr()) { // An exported function will always be emitted when defined, so even if // the function is inline, it doesn't have to be emitted in this TU. An - // imported function implies that it has been exported somewhere else. + // imported function implies that it has been exported somewhere else, + // except if it is marked with exclude_from_explicit_instantiation, in + // which case there might not be a definition elsewhere. continue; } diff --git a/clang/test/CodeGenCXX/attr-exclude_from_explicit_instantiation.dllimport.cpp b/clang/test/CodeGenCXX/attr-exclude_from_explicit_instantiation.dllimport.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/attr-exclude_from_explicit_instantiation.dllimport.cpp @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-windows -fms-extensions -emit-llvm -O0 -o - %s | FileCheck %s + +// Test that dllimport and exclude_from_explicit_instantiation work properly +// together. Specifically, we check that when exclude_from_explicit_instantiation +// is used on a method, the compiler doesn't expect it to be provided externally +// even if it is marked with dllimport. +// +// https://github.com/llvm/llvm-project/issues/40363 + +#define DLLIMPORT __declspec(dllimport) +#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation)) + +template +struct DLLIMPORT Foo { + EXCLUDE_FROM_EXPLICIT_INSTANTIATION void x() {} +}; + +template +struct Bar { + DLLIMPORT EXCLUDE_FROM_EXPLICIT_INSTANTIATION void x() {} +}; + +extern template struct Foo; +extern template struct Bar; + +void test(Foo& foo, Bar& bar) { + // CHECK-DAG: define linkonce_odr dso_local void @"?x@?$Foo@H@@QEAAXXZ" + foo.x(); + + // CHECK-DAG: define linkonce_odr dso_local void @"?x@?$Bar@H@@QEAAXXZ" + bar.x(); +} diff --git a/clang/test/SemaCXX/attr-exclude_from_explicit_instantiation.dllimport.cpp b/clang/test/SemaCXX/attr-exclude_from_explicit_instantiation.dllimport.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-exclude_from_explicit_instantiation.dllimport.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-windows -fms-extensions -verify %s + +// Test that an entity marked as both dllimport and exclude_from_explicit_instantiation +// isn't instantiated. + +#define DLLIMPORT __declspec(dllimport) +#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation)) + +template +struct DLLIMPORT Foo { + EXCLUDE_FROM_EXPLICIT_INSTANTIATION void x(); +}; + +template +struct Bar { + DLLIMPORT EXCLUDE_FROM_EXPLICIT_INSTANTIATION inline void x(); +}; + +template +void Foo::x() { using Fail = typename T::fail; } + +template +DLLIMPORT inline void Bar::x() { using Fail = typename T::fail; } + +// expected-no-diagnostics +template struct Foo; +template struct Bar;