https://github.com/kikairoya updated https://github.com/llvm/llvm-project/pull/140145
>From 37af003a4cc9afb74a05cddd5118ed0695cec632 Mon Sep 17 00:00:00 2001 From: kikairoya <kikair...@gmail.com> Date: Mon, 21 Apr 2025 23:30:13 +0900 Subject: [PATCH 1/2] [Cygwin][MinGW] Internal class in explicitly-instantiation-declarated template should be instantiated In-code comment says "explicit instantiation decl of the outer class doesn't affect the inner class" but this behavior seems MSVC specific, MinGW-GCC and Cygwin-GCC does not. Clang should honor gcc's behavior. This change fixes std::string compatibilty and resolves strange link error (statically linked), strange crash (dynamically linked) using libstdc++ on Cygwin. --- clang/lib/Sema/SemaTemplateInstantiate.cpp | 1 + .../CodeGenCXX/mingw-template-dllexport.cpp | 109 +++++++++++++++--- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index d028eea4f8f3e..b7c27b3795f5d 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -4247,6 +4247,7 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation, continue; if (Context.getTargetInfo().getTriple().isOSWindows() && + !Context.getTargetInfo().getTriple().isOSCygMing() && TSK == TSK_ExplicitInstantiationDeclaration) { // On Windows, explicit instantiation decl of the outer class doesn't // affect the inner class. Typically extern template declarations are diff --git a/clang/test/CodeGenCXX/mingw-template-dllexport.cpp b/clang/test/CodeGenCXX/mingw-template-dllexport.cpp index de112d6da53db..a6047b5955e96 100644 --- a/clang/test/CodeGenCXX/mingw-template-dllexport.cpp +++ b/clang/test/CodeGenCXX/mingw-template-dllexport.cpp @@ -6,46 +6,121 @@ #define JOIN2(x, y) x##y #define JOIN(x, y) JOIN2(x, y) #define UNIQ(name) JOIN(name, __LINE__) -#define USEMEMFUNC(class, func) void (class::*UNIQ(use)())() { return &class::func; } +#define USEMEMFUNC(class, func) auto UNIQ(use) = &class::func; template <class T> class c { + // MinGW-GCC does not apply 'dllexport' to inline member function in dll-exported template but clang does from long ago. void f() {} + void g(); + inline static int u = 0; + static int v; }; +template <class T> void c<T>::g() {} +template <class T> int c<T>::v = 0; +// #1 template class __declspec(dllexport) c<int>; -// CHECK: define {{.*}} dllexport {{.*}} @_ZN1cIiE1fEv - +// #2 extern template class __declspec(dllexport) c<char>; template class c<char>; -// CHECK: define {{.*}} dllexport {{.*}} @_ZN1cIcE1fEv - +// #3 extern template class c<double>; -template class __declspec(dllexport) c<double>; +template class __declspec(dllexport) c<double>; // expected-warning {{ 'dllexport' attribute ignored on explicit instantiation definition }} -// CHECK-NOT: define {{.*}} dllexport {{.*}} @_ZN1cIdE1fEv template <class T> struct outer { - void f(); + void f() {} + void g(); + inline static int u = 0; + static int v; + // MinGW-GCC and Clang does not apply 'dllexport' to inner type and its sub-elements in template class. struct inner { - void f(); + void f() {} + void g(); + inline static int u = 0; + static int v; }; }; -template <class T> void outer<T>::f() {} -template <class T> void outer<T>::inner::f() {} +template <class T> void outer<T>::g() {} +template <class T> void outer<T>::inner::g() {} +template <class T> int outer<T>::v = 0; +template <class T> int outer<T>::inner::v = 0; -template class __declspec(dllexport) outer<int>; +// #4 +template struct __declspec(dllexport) outer<int>; -// CHECK: define {{.*}} dllexport {{.*}} @_ZN5outerIiE1fEv -// CHECK-NOT: define {{.*}} dllexport {{.*}} @_ZN5outerIiE5inner1fEv - -extern template class __declspec(dllimport) outer<char>; +// #5 +extern template struct __declspec(dllimport) outer<char>; USEMEMFUNC(outer<char>, f) +USEMEMFUNC(outer<char>, g) +USEMEMFUNC(outer<char>, u) +USEMEMFUNC(outer<char>, v) USEMEMFUNC(outer<char>::inner, f) +USEMEMFUNC(outer<char>::inner, g) +USEMEMFUNC(outer<char>::inner, u) +USEMEMFUNC(outer<char>::inner, v) + + +// #1 variables +// CHECK: @_ZN1cIiE1uE = {{.*}} dllexport {{.*}} +// CHECK: @_ZN1cIiE1vE = {{.*}} dllexport {{.*}} + +// #2 variables +// CHECK: @_ZN1cIcE1uE = {{.*}} dllexport {{.*}} +// CHECK: @_ZN1cIcE1vE = {{.*}} dllexport {{.*}} + +// #3 variables +// CHECK: @_ZN1cIdE1uE = {{.*}} +// CHECK-NOT: @_ZN1cIcE1uE = {{.*}} dllexport {{.*}} +// CHECK: @_ZN1cIdE1vE = {{.*}} +// CHECK-NOT: @_ZN1cIcE1vE = {{.*}} dllexport {{.*}} + +// #4 variables +// CHECK: @_ZN5outerIiE1uE = {{.*}} dllexport {{.*}} +// CHECK: @_ZN5outerIiE1vE = {{.*}} dllexport {{.*}} +// CHECK: @_ZN5outerIiE5inner1uE = {{.*}} +// CHECK-NOT: @_ZN5outerIiE5inner1uE = {{.*}} dllexport {{.*}} +// CHECK: @_ZN5outerIiE5inner1vE = {{.*}} +// CHECK-NOT: @_ZN5outerIiE5inner1vE = {{.*}} dllexport {{.*}} + +// #5 variables +// CHECK: @_ZN5outerIcE1uE = external dllimport {{.*}} +// CHECK: @_ZN5outerIcE1vE = external dllimport {{.*}} +// CHECK-NOT: @_ZN5outerIcE5inner1uE = dllimport {{.*}} +// CHECK-NOT: @_ZN5outerIcE5inner1vE = dllimport {{.*}} +// CHECK: @_ZN5outerIcE5inner1uE = external {{.*}} +// CHECK: @_ZN5outerIcE5inner1vE = external {{.*}} + + +// #1 functions +// CHECK: define {{.*}} dllexport {{.*}} @_ZN1cIiE1fEv +// CHECK: define {{.*}} dllexport {{.*}} @_ZN1cIiE1gEv + +// #2 functions +// CHECK: define {{.*}} dllexport {{.*}} @_ZN1cIcE1fEv +// CHECK: define {{.*}} dllexport {{.*}} @_ZN1cIcE1gEv + +// #3 functions +// CHECK-NOT: define {{.*}} dllexport {{.*}} @_ZN1cIdE1fEv +// CHECK-NOT: define {{.*}} dllexport {{.*}} @_ZN1cIdE1gEv + +// #4 functions +// CHECK: define {{.*}} dllexport {{.*}} @_ZN5outerIiE1fEv +// CHECK: define {{.*}} dllexport {{.*}} @_ZN5outerIiE1gEv +// CHECK-NOT: define {{.*}} dllexport {{.*}} @_ZN5outerIiE5inner1fEv +// CHECK-NOT: define {{.*}} dllexport {{.*}} @_ZN5outerIiE5inner1gEv +// #5 functions // CHECK: declare dllimport {{.*}} @_ZN5outerIcE1fEv -// CHECK: define {{.*}} @_ZN5outerIcE5inner1fEv +// CHECK: declare dllimport {{.*}} @_ZN5outerIcE1gEv +// CHECK-NOT: declare dllimport {{.*}} @_ZN5outerIcE5inner1fEv +// CHECK-NOT: declare dllimport {{.*}} @_ZN5outerIcE5inner1gEv +// CHECK-NOT: define {{.*}} @_ZN5outerIcE1fEv +// CHECK-NOT: define {{.*}} @_ZN5outerIcE5inner1fEv +// CHECK-NOT: define {{.*}} @_ZN5outerIcE1gEv +// CHECK-NOT: define {{.*}} @_ZN5outerIcE5inner1gEv >From 9349b45cdb162d7a1ccd79aa0e9d4cb87cac3505 Mon Sep 17 00:00:00 2001 From: kikairoya <kikair...@gmail.com> Date: Tue, 20 May 2025 19:13:33 +0900 Subject: [PATCH 2/2] [libc++] Introduce a new attribute keyword for Clang improves compatibility with Mingw-GCC This is affected to only Clang for MinGW target. In MinGW environment, Clang handles dllexport attribute of internal class that defined in class template in different way from GCC. This incompatibility should be fixed but breaks ABI of libc++, so introduce a new keyword to keep ABI in MinGW environment with old and patched Clang and to ensure nothing affects to other environment/platforms. The new attribute keyword _LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS does nothing in almost all situations except if included from client (not in building libc++ itself) by clang (not by GCC or others). If clang does include libc++, the keyword will be expanded to __attribute__((__exclude_from_explicit_instantiation__)), results attached class will be away from explicit instantiation declaration so will be instanciated implicitly as formar Clang does. Thus, it will no-op for old Clang that has incompatibility with MinGW-GCC or emulate old behavior for patched Clang. This attribute is attached only for std::basic_ostream::sentry and std::basic_istream::sentry. Other entities won't be affected by patching Clang so doesn't need to be annotate. Notably, at a time to introduce a new class as a non-template inner type of a class template, that class also needs to be attached _LIBCPP_INNER_CLASS_ININ_TEMPLATE_VIS. --- libcxx/docs/DesignDocs/VisibilityMacros.rst | 7 +++++++ libcxx/include/__config | 8 ++++++++ libcxx/include/__ostream/basic_ostream.h | 2 +- libcxx/include/istream | 2 +- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/libcxx/docs/DesignDocs/VisibilityMacros.rst b/libcxx/docs/DesignDocs/VisibilityMacros.rst index e9f88118b7ef3..68e5430bd4a33 100644 --- a/libcxx/docs/DesignDocs/VisibilityMacros.rst +++ b/libcxx/docs/DesignDocs/VisibilityMacros.rst @@ -93,6 +93,13 @@ Visibility Macros the extern template declaration) as exported on Windows, as discussed above. On all other platforms, this macro has an empty definition. +**_LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS** + To let dll-importing and dll-exporting consitent on windows-gnu environment, + this annotation must be attached to a non-template class that isn't marked + as `dllexport` defined in a class template. Currentry here are ostream::sentry + and istream::sentry only but if a time to such a new class be introduced, + the class has to be attached this annotation. + Links ===== diff --git a/libcxx/include/__config b/libcxx/include/__config index 110450f6e9c51..91b75860c84ef 100644 --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -365,6 +365,11 @@ typedef __char32_t char32_t; # define _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS # define _LIBCPP_OVERRIDABLE_FUNC_VIS # define _LIBCPP_EXPORTED_FROM_ABI +# if defined(_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS) || !__has_attribute(exclude_from_explicit_instantiation) +# define _LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS +# else +# define _LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS __attribute__((__exclude_from_explicit_instantiation__)) +# endif # elif defined(_LIBCPP_BUILDING_LIBRARY) # if defined(__MINGW32__) # define _LIBCPP_EXTERN_TEMPLATE_TYPE_VIS __declspec(dllexport) @@ -375,11 +380,13 @@ typedef __char32_t char32_t; # endif # define _LIBCPP_OVERRIDABLE_FUNC_VIS __declspec(dllexport) # define _LIBCPP_EXPORTED_FROM_ABI __declspec(dllexport) +# define _LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS # else # define _LIBCPP_EXTERN_TEMPLATE_TYPE_VIS __declspec(dllimport) # define _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS # define _LIBCPP_OVERRIDABLE_FUNC_VIS # define _LIBCPP_EXPORTED_FROM_ABI __declspec(dllimport) +# define _LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS # endif # define _LIBCPP_HIDDEN @@ -399,6 +406,7 @@ typedef __char32_t char32_t; # define _LIBCPP_EXPORTED_FROM_ABI _LIBCPP_VISIBILITY("default") # define _LIBCPP_EXTERN_TEMPLATE_TYPE_VIS _LIBCPP_VISIBILITY("default") # define _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS +# define _LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS // TODO: Make this a proper customization point or remove the option to override it. # ifndef _LIBCPP_OVERRIDABLE_FUNC_VIS diff --git a/libcxx/include/__ostream/basic_ostream.h b/libcxx/include/__ostream/basic_ostream.h index f7473a36d8ccc..a72350501ccda 100644 --- a/libcxx/include/__ostream/basic_ostream.h +++ b/libcxx/include/__ostream/basic_ostream.h @@ -71,7 +71,7 @@ class basic_ostream : virtual public basic_ios<_CharT, _Traits> { public: // 27.7.2.4 Prefix/suffix: - class sentry; + class _LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS sentry; // 27.7.2.6 Formatted output: inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 basic_ostream& operator<<(basic_ostream& (*__pf)(basic_ostream&)) { diff --git a/libcxx/include/istream b/libcxx/include/istream index 95340c739c118..b41dd59060f78 100644 --- a/libcxx/include/istream +++ b/libcxx/include/istream @@ -228,7 +228,7 @@ public: basic_istream& operator=(const basic_istream& __rhs) = delete; // 27.7.1.1.3 Prefix/suffix: - class sentry; + class _LIBCPP_INNER_CLASS_IN_TEMPLATE_VIS sentry; // 27.7.1.2 Formatted input: inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 basic_istream& operator>>(basic_istream& (*__pf)(basic_istream&)) { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits