llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.52k stars 11.79k forks source link

Clang fails to dllexport template specialization in C++20 if it's used in the same TU #86958

Open aganea opened 6 months ago

aganea commented 6 months ago

When building with clang-cl.exe, the following wouldn't export foo<0>. This works with MSVC cl.exe.

// a.cpp
struct A {
template <bool T > static bool __declspec(dllexport) foo();
};

template <typename T>
T b() { return A::foo<false>() ? 1 : 0; }

template<> bool __declspec(dllexport) A::foo<false>() { return false; }

int main() {
   return b<int>();
}

No symbols are exported:

> clang-cl.exe a.cpp /c /std:c++20
> dumpbin /all a.obj | grep EXPORT
(empty stdout)

Removing /std:c++20 from the cmd-line would properly export the specialization foo<0>:

> clang-cl.exe a.cpp /c
> dumpbin /all a.obj | grep EXPORT
  00000030: 20 2F 45 58 50 4F 52 54 3A 22 3F 3F 24 66 6F 6F   /EXPORT:"??$foo
   /EXPORT:??$foo@$0A@@A@@SA_NXZ

Declaring the specialization in the struct would export the specialization as well:

// a.cpp
struct A {
template <bool T > static bool __declspec(dllexport) foo();
template<> bool __declspec(dllexport) foo<false>();
};

template <typename T>
T b() { return A::foo<false>() ? 1 : 0; }

template<> bool __declspec(dllexport) A::foo<false>() { return false; }

int main() {
   return b<int>();
}

Moving the specialization before b() works too:

// a.cpp
struct A {
template <bool T > static bool __declspec(dllexport) foo();
};

template<> bool __declspec(dllexport) A::foo<false>() { return false; }

template <typename T>
T b() { return A::foo<false>() ? 1 : 0; }

int main() {
   return b<int>();
}

Also if main() doesn't call b() the specialization would be exported as well:

// a.cpp
struct A {
template <bool T > static bool __declspec(dllexport) foo();
};

template <typename T>
T b() { return A::foo<false>() ? 1 : 0; }

template<> bool __declspec(dllexport) A::foo<false>() { return false; }

int main() {
   return 0;
}

+@zmodem Since you've worked in this area in the past.

aganea commented 6 months ago

I don't quite understand why we're calling StripImplicitInstantiation() in SemaTemplate.cpp from CheckSpecializationInstantiationRedecl(). I'm seeing PrevTSK = TSK_ImplicitInstantiation but clearly in the example above it isn't an implicit instantiation.

aganea commented 6 months ago

This seem to work pre-C++20 because of -fdelayed-template-parsing

shafik commented 6 months ago

CC @AaronBallman

llvmbot commented 6 months ago

@llvm/issue-subscribers-c-20

Author: Alexandre Ganea (aganea)

When building with `clang-cl.exe`, the following wouldn't export `foo<0>`. This works with MSVC `cl.exe`. ``` // a.cpp struct A { template <bool T > static bool __declspec(dllexport) foo(); }; template <typename T> T b() { return A::foo<false>() ? 1 : 0; } template<> bool __declspec(dllexport) A::foo<false>() { return false; } int main() { return b<int>(); } ``` No symbols are exported: ``` > clang-cl.exe a.cpp /c /std:c++20 > dumpbin /all a.obj | grep EXPORT (empty stdout) ``` Removing `/std:c++20` from the cmd-line would properly export the specialization `foo<0>`: ``` > clang-cl.exe a.cpp /c > dumpbin /all a.obj | grep EXPORT 00000030: 20 2F 45 58 50 4F 52 54 3A 22 3F 3F 24 66 6F 6F /EXPORT:"??$foo /EXPORT:??$foo@$0A@@A@@SA_NXZ ``` Declaring the specialization in the struct would export the specialization as well: ``` // a.cpp struct A { template <bool T > static bool __declspec(dllexport) foo(); template<> bool __declspec(dllexport) foo<false>(); }; template <typename T> T b() { return A::foo<false>() ? 1 : 0; } template<> bool __declspec(dllexport) A::foo<false>() { return false; } int main() { return b<int>(); } ``` Moving the specialization before `b()` works too: ``` // a.cpp struct A { template <bool T > static bool __declspec(dllexport) foo(); }; template<> bool __declspec(dllexport) A::foo<false>() { return false; } template <typename T> T b() { return A::foo<false>() ? 1 : 0; } int main() { return b<int>(); } ``` Also if `main()` doesn't call `b()` the specialization would be exported as well: ``` // a.cpp struct A { template <bool T > static bool __declspec(dllexport) foo(); }; template <typename T> T b() { return A::foo<false>() ? 1 : 0; } template<> bool __declspec(dllexport) A::foo<false>() { return false; } int main() { return 0; } ``` +@zmodem Since you've worked in this area in the past.
AaronBallman commented 6 months ago

CC @erichkeane as well

AaronBallman commented 6 months ago

I can confirm the behavior: https://godbolt.org/z/EYh8aY3Gq

C++20: define dso_local noundef zeroext i1 @"??$foo@$0A@@A@@SA_NXZ"() align 2 Otherwise: define dso_local dllexport noundef zeroext i1 @"??$foo@$0A@@A@@SA_NXZ"() align 2