Bug 110572 - ld.lld: error: duplicate symbol: std::type_info::operator==(std::type_info const&) const
Summary: ld.lld: error: duplicate symbol: std::type_info::operator==(std::type_info co...
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: unknown
: P3 normal
Target Milestone: 12.5
Assignee: Not yet assigned to anyone
URL:
Keywords: ABI
Depends on:
Blocks:
 
Reported: 2023-07-06 11:07 UTC by Thomas Arndt
Modified: 2024-06-24 18:45 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2023-07-06 00:00:00


Attachments
example to reproduce the error (506 bytes, application/x-zip-compressed)
2023-07-06 11:07 UTC, Thomas Arndt
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Thomas Arndt 2023-07-06 11:07:39 UTC
Created attachment 55489 [details]
example to reproduce the error

I've filed the issue since it's a problem with libstdc++/tinfo.o.
It seems there is a missing inline if macro __GXX_TYPEINFO_EQUALITY_INLINE is unset or set to 1.

It happens if clang is used under mingw-w64 in combination with the libstdc++ (msys2 default).
Since gcc defines the macro explicitly (built-in) to _GXX_TYPEINFO_EQUALITY_INLINE=0 (see gcc/config/i386/cygming.h).
Clang doesn't define this macro at all, so it gets defined within typeinfo while compiling user code. Thus there are one operator== defined within tinfo.o and another within the user archive.

During linking the following error pops up:

ld.lld: error: duplicate symbol: std::type_info::operator==(std::type_info const&) const
>>> defined at ../../../../libstdc++-v3/libsupc++/tinfo.cc:38
>>>            libstdc++.a(tinfo.o)
>>> defined at libclang_lld_error.a(comp_unit.o)
clang++: error: linker command failed with exit code 1 (use -v to see invocation)

Here is the full example to reproduce:
https://github.com/msys2/MINGW-packages/issues/17730

I fully agree, it's a issue which is more related to mingw-w64 and clang but the root cause is unfortunately the libstdc++.
Comment 1 Jonathan Wakely 2023-07-06 11:34:57 UTC
I would argue that the root cause is that Clang does not conform to the platform ABI for mingw-w64, which requires __GXX_TYPEINFO_EQUALITY_INLINE=0 to be defined.
Comment 2 Jonathan Wakely 2023-07-06 13:11:45 UTC
Prior to r12-6266-g3633cc54284450 making the definition in tinfo.cc inline would have meant it is never emitted into tinfo.o (unless __attribute__((used)) was added to it).

Since the __equal alias, the operator== definition is always used, so it will still generate a symbol even if it's an inline function. But I'm not sure adding 'inline' there will actually fix anything. I still see it produce the same symbol whether inline or not:

0000000000000000 T _ZNKSt9type_info7__equalERKS_
0000000000000000 T _ZNKSt9type_infoeqERKS_

I'm also concerned that the __equal function could end up in infinite recursion. If you compile C++20 code then the inline definition of type_info::operator== in the header is used, which calls type_info::__equal, which is an alias for type_info::operator==. If the alias doesn't bind locally then it could call the inline definition from the header again, and recurse.

I think a better fix would be to declare the operator== in the header with __attribute__((__always_inline__)). That will ensure there is never a symbol emitted for that inline definition, and so no multiple definition errors.
Comment 3 Jonathan Wakely 2023-07-06 13:14:45 UTC
N.B. this can be reproduced without clang, just by using -std=c++20 -static-libstdc++

/usr/bin/x86_64-w64-mingw32-ld: /home/jwakely/gcc/mingw/lib/gcc/x86_64-w64-mingw32/13.0.1/../../../../x86_64-w64-mingw32/lib/../lib/libstdc++.a(tinfo.o): in function `std::type_info::operator==(std::type_info const&) const':
/home/jwakely/src/gcc/build-mingw64/x86_64-w64-mingw32/libstdc++-v3/libsupc++/../../../../gcc/libstdc++-v3/libsupc++/tinfo.cc:61: multiple definition of `std::type_info::operator==(std::type_info const&) const'; /tmp/ccmMqprE.o:/home/jwakely/gcc/mingw/x86_64-w64-mingw32/include/c++/13.0.1/typeinfo:194: first defined here
collect2: error: ld returned 1 exit status
Comment 4 Thomas Arndt 2023-07-10 08:14:50 UTC
Sounds good, I would agree on your solution as well. So since it's not related to clang and can be reproduced with gcc is there a fix planned?
Comment 5 Peter Damianov 2023-09-01 21:30:17 UTC
#include <typeinfo>
int main() { return typeid(0) == typeid(0); }

The following reproduces for me, although strangely only with -std=c++23 and -static-libstdc++.

x86_64-w64-mingw32-g++ test.cpp -static-libstdc++ -std=c++20
// no error

x86_64-w64-mingw32-g++ test.cpp -static-libstdc++ -std=c++23
/usr/lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld: /usr/lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/lib/../lib/libstdc++.a(tinfo.o): in function `std::type_info::operator==(std::type_info const&) const':
/build/mingw-w64-gcc/src/gcc/libstdc++-v3/libsupc++/tinfo.cc:42: multiple definition of `std::type_info::operator==(std::type_info const&) const'; /tmp/ccyAJVlk.o:test.cpp:(.text$_ZNKSt9type_infoeqERKS_[_ZNKSt9type_infoeqERKS_]+0x0): first defined here

Related issue:
https://github.com/skeeto/w64devkit/issues/86
Comment 6 Jonathan Wakely 2024-05-31 13:23:48 UTC
(In reply to Peter Damianov from comment #5)
> #include <typeinfo>
> int main() { return typeid(0) == typeid(0); }
> 
> The following reproduces for me, although strangely only with -std=c++23 and
> -static-libstdc++.

Because it's constexpr (and so always inline) in C++23, and not in C++20.

I think I probably meant to type c++23 in comment 3.
Comment 7 Jonathan Wakely 2024-05-31 13:44:19 UTC
This is the fix I'm suggesting:

--- a/libstdc++-v3/libsupc++/typeinfo
+++ b/libstdc++-v3/libsupc++/typeinfo
@@ -188,6 +188,9 @@ namespace std
 #endif
 
 #if __GXX_TYPEINFO_EQUALITY_INLINE || __cplusplus > 202002L
+# if ! __GXX_TYPEINFO_EQUALITY_INLINE
+  [[__gnu__::__always_inline__]]
+# endif
   _GLIBCXX23_CONSTEXPR inline bool
   type_info::operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
   {
Comment 8 Peter Damianov 2024-05-31 14:14:11 UTC
The suggested fix works for g++, but not clang++.
Comment 9 Jonathan Wakely 2024-05-31 14:17:39 UTC
Right, for Clang we need:

--- a/libstdc++-v3/libsupc++/typeinfo
+++ b/libstdc++-v3/libsupc++/typeinfo
@@ -73,7 +73,7 @@ namespace __cxxabiv1
 
 // By default follow the old inline rules to avoid ABI changes.
 #ifndef __GXX_TYPEINFO_EQUALITY_INLINE
-#  if !__GXX_WEAK__
+#  if !__GXX_WEAK__ || defined(_WIN32)
 #    define __GXX_TYPEINFO_EQUALITY_INLINE 0
 #  else
 #    define __GXX_TYPEINFO_EQUALITY_INLINE 1
@@ -188,6 +188,9 @@ namespace std
 #endif
 
 #if __GXX_TYPEINFO_EQUALITY_INLINE || __cplusplus > 202002L
+#if ! __GXX_TYPEINFO_EQUALITY_INLINE
+  [[__gnu__::__always_inline__]]
+# endif
   _GLIBCXX23_CONSTEXPR inline bool
   type_info::operator==(const type_info& __arg) const _GLIBCXX_NOEXCEPT
   {


Because otherwise the macro gets defined to 1 and so the always_inline attribute isn't used.
Comment 10 Jonathan Wakely 2024-05-31 14:18:57 UTC
(In reply to Jonathan Wakely from comment #1)
> I would argue that the root cause is that Clang does not conform to the
> platform ABI for mingw-w64, which requires __GXX_TYPEINFO_EQUALITY_INLINE=0
> to be defined.

I stand by this comment though. If Clang wants to be able to use libstdc++ headers on Windows, it should be compatible (i.e. define the same ABI-impacting macros) with g++ on Windows.
Comment 11 Jonathan Wakely 2024-05-31 14:29:43 UTC
CC Martin Storsjo to see if changing Clang would be possible, or if he has a better idea for the preprocessor check suggested in comment 9.

It might be that Clang can't pre-define this macro because it has different values for different mingw/mingw-w64/w64devkit toolchains, and clang wouldn't know which one to be compatible with a priori.
Comment 12 Martin Storsjö 2024-06-03 11:49:34 UTC
(In reply to Jonathan Wakely from comment #11)
> CC Martin Storsjo to see if changing Clang would be possible, or if he has a
> better idea for the preprocessor check suggested in comment 9.
> 
> It might be that Clang can't pre-define this macro because it has different
> values for different mingw/mingw-w64/w64devkit toolchains, and clang
> wouldn't know which one to be compatible with a priori.

Thanks for looping me in!


In this case, as this seems to be a fixed configuration for libstdc++ (as gcc/config/i386/cygming.h hardcodes __GXX_TYPEINFO_EQUALITY_INLINE=0), I don't see a problem with adding this define to Clang. Clang does have a couple of __GXX_* defines from before, but none that are target specific like this.

Next to this one, in gcc/config/i386/cygming.h, I also see __GXX_MERGED_TYPEINFO_NAMES=0, I presume we should add that too, if we add __GXX_TYPEINFO_EQUALITY_INLINE.


For Clang/libstdc++ interop on mingw in general, there are a couple of other longstanding issues, where Clang defaults to -fno-emulated-tls, while GCC defaults to -femulated-tls, and libstdc++ exposes a couple of TLS symbols directly in the library ABI surface, e.g. in __once_callable - see e.g. https://github.com/msys2/MINGW-packages/issues/8706. If those TLS symbols were to be moved out of headers, accessed only through accessor functions, this wouldn't be an issue (at the cost of a little bit of extra performance/indirection - although cross-translation unit access of TLS variables is more complex than for TLS variables marked static).

For that particular issue, I've raised the question whether Clang should switch to emulated TLS by default, but the general opinion I've gathered (among e.g. msys2 users) is that we shouldn't. There's also been talks about getting native TLS implemented in GCC.

Msys2 works around this issue by patching LLVM, for the environments that operate with libstdc++: https://github.com/msys2/MINGW-packages/blob/f6e5319a54f9657ee190f9e0ea1c8d59b7d77a62/mingw-w64-llvm/0004-enable-emutls-for-mingw.patch https://github.com/msys2/MINGW-packages/blob/f6e5319a54f9657ee190f9e0ea1c8d59b7d77a62/mingw-w64-llvm/PKGBUILD#L140-L144

Similarly, there's also an issue around whether pthreads is a default-linked library or not. As GCC can be built either with win32 or posix thread model, both scenarios are common, so msys2 also carries such an optional patch for making pthreads automatically linked in, in the relevant msys2 environments.


But back to the topic of __GXX_TYPEINFO_EQUALITY_INLINE - as this seems to not vary across known configurations, I think it should be doable to add the definition to Clang.
Comment 13 GCC Commits 2024-06-14 14:54:31 UTC
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:6af8d8e618ed27dae3432c96484de4360bd893ab

commit r15-1342-g6af8d8e618ed27dae3432c96484de4360bd893ab
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Tue Jun 11 15:52:30 2024 +0100

    libstdc++: Make std::type_info::operator== always_inline for C++23 [PR110572]
    
    Commit r12-6266-g3633cc54284450 implemented P1328 for C++23, making
    std::type_info::operator== usable in constant expressions. For targets
    such as mingw-w64 where that function was not previously inline, making
    it constexpr required making it inline for C++23 and later. For
    statically linked programs this can result in multiple definition
    errors, because there's a non-inline definition in libstdc++.a as well.
    
    For those targets make it always_inline for C++23, so that there is no
    symbol generated for the inline definition, and the non-inline
    definition in libstdc++.a will be the only definition.
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/110572
            * libsupc++/typeinfo (type_info::operator==): Add always_inline
            attribute for targets where the ABI requries equality to be
            non-inline.
            * testsuite/18_support/type_info/110572.cc: New test.
Comment 14 Martin Storsjö 2024-06-14 16:10:01 UTC
(In reply to GCC Commits from comment #13)
> The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:
> 
> https://gcc.gnu.org/g:6af8d8e618ed27dae3432c96484de4360bd893ab
> 
> commit r15-1342-g6af8d8e618ed27dae3432c96484de4360bd893ab
> Author: Jonathan Wakely <jwakely@redhat.com>
> Date:   Tue Jun 11 15:52:30 2024 +0100
> 
>     libstdc++: Make std::type_info::operator== always_inline for C++23
> [PR110572]

Is this enough to fix the issue with Clang, or should I still look into making Clang predefine __GXX_TYPEINFO_EQUALITY_INLINE=0 for mingw targets? (Even if it works, it's admittedly not very pretty to rely on "#if !FOO" for an undefined FOO.)
Comment 15 Jonathan Wakely 2024-06-14 16:33:16 UTC
No, this only helps for mingw-g++. Only the change from comment 7 has been committed. As noted in comment 9, the incorrect macro definition for clang still causes problems.  We either need to work around that like in the first bit of comment 9, or clang needs a change.
Comment 16 Richard Biener 2024-06-20 09:13:17 UTC
GCC 12.4 is being released, retargeting bugs to GCC 12.5.