Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSYS2: Export forwarders for __cxa_begin_catch, __cxa_end_catch, __cxa_rethrow and __gxx_personality_seh0 #279

Closed

Conversation

qmfrederik
Copy link
Collaborator

libobjc2 uses native C++ exceptions on MinGW. The clang compiler will emit references to __cxa_begin_catch, __cxa_end_catch, __cxa_rethrow and __gxx_personality_seh0 for Objective C code which uses Objective C exceptions.

These symbols are defined in the C++ runtime, not in libobjc2. As a result, merely linking with libobjc2 is not sufficient. Objective C code such as GNUstep must be compiled with the LDFLAGS="-lgcc_s -lstdc++" or LDFLAGS="-lc++", depending on the environment.

This is tedious. Additionally, specifying -lc++ on the msys/clang64 environment causes linker errors:

 Linking library libgnustep-base ...
ld.lld: error: libc++.dll.a(libc++.dll): .idata$4 should not refer to special section 0

A similar error has been observed for other libraries

A solution for this is to define forwarding exports for __cxa_begin_catch, __cxa_end_catch, __cxa_rethrow and __gxx_personality_seh0. This is implemented by adding a eh_forwards.def file to the list of libobjc2 source files, which forwards the symbols to the actual C++ runtime. On MSYS2, the libstdc++ and libc++ runtimes are supported, which covers all MinGW environments: https://www.msys2.org/docs/environments/.

Forwarding exports are discussed here:

…`__cxa_rethrow` and `__gxx_personality_seh0`

libobjc2 uses native C++ exceptions on MinGW.  The clang compiler will emit references to `__cxa_begin_catch`, `__cxa_end_catch`, `__cxa_rethrow` and `__gxx_personality_seh0` for Objective C code which uses Objective C exceptions.

These symbols are defined in the C++ runtime, not in libobjc2.  As a result, merely linking with libobjc2 is not sufficient.  Objective C code such as GNUstep must be compiled with the `LDFLAGS="-lgcc_s -lstdc++"` or `LDFLAGS="-lc++"`, depending on the environment.

This is tedious.  Additionally, specifying `-lc++` on the msys/clang64 environment causes linker errors:

```
 Linking library libgnustep-base ...
ld.lld: error: libc++.dll.a(libc++.dll): .idata$4 should not refer to special section 0
```

A [similar error has been observed for other libraries](msys2/MINGW-packages#18589)

A solution for this is to define forwarding exports for `__cxa_begin_catch`, `__cxa_end_catch`, `__cxa_rethrow` and `__gxx_personality_seh0`.  This is implemented by adding a `eh_forwards.def` file to the list of libobjc2 source files, which forwards the symbols to the actual C++ runtime.  On MSYS2, the libstdc++ and libc++ runtimes are supported, which covers all MinGW environments: https://www.msys2.org/docs/environments/.

Forwarding exports are discussed here:
- https://learn.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-170
- https://devblogs.microsoft.com/oldnewthing/20060719-24/?p=30473
- https://devblogs.microsoft.com/oldnewthing/20121116-00/?p=6073
@qmfrederik
Copy link
Collaborator Author

@MehdiChinoune - FYI. This is my best attempt at removing the need to set LDFLAGS (other than -fuse-ld=lld) on all platforms, and avoid a linker crash on clang64. One thing which bugs me is that I'd like to get the name of the shared library of the C++ runtime (e.g. libstdc++-6). I can use find_library(CXX_RUNTIME "stdc++" REQUIRED) but this will get me the name of the import lbirary (libstd++). Do you know of a way to get the actual runtime library name in CMake?

@davidchisnall It's a bit clunky, but it does work. The root problem this PR is trying to address is that ObjC code will generate calls to __cxa_begin_catch and friends, but they are not part of libobjc2, they are in the C++ runtime. An alternative is to declare wrapper functions in libobjc2, such as objc_begin_catch, which are just wrappers around the C++ runtime equivalent, and have clang emit calls to the objc_begin_catch instead?

@davidchisnall
Copy link
Member

Option 2 might be better for modularity. Unfortunately, LLVM 18 has now branched and so we will have to support both options unless you can get the change in before it ships.

@qmfrederik
Copy link
Collaborator Author

Getting those changes in LLVM 18 while they have already declared release candidates feels like a stretch. What if I amend this PR so that it declares those functions, and we can work to get an update in the next release of LLVM? We'll have to be hybrid for a while, but MSYS2 is a fast-moving rolling release so the forwarders could be deprecated in the next 6 months or so.

I assume the API shape would look like this:

libobjc2 method façade for
objc_begin_catch __cxa_begin_catch
objc_end_catch __cxa_end_catch
objc_exception_rethrow __cxa_rethrow
__gnustep_objcxx_personality_seh0 __gxx_personality_seh0

The rethrow method is named objc_exception_rethrow and not objc_rethrow, in line with Apple's implementation. An alternative name for the personality method would be __objc_personality_v0 or __objc_personality_seh0, which would match the Apple implementation.

qmfrederik added a commit to qmfrederik/MINGW-packages that referenced this pull request Feb 14, 2024
Backport two patches from libobjc2:
- Fix uncaught exception handling (gnustep/libobjc2#278)
- Export forwarders for C++ methods, so that we no longer need to link with the C++ runtime (gnustep/libobjc2#279)
@@ -23,32 +29,33 @@ void *__cxa_allocate_exception(size_t thrown_size) CXA_ALLOCATE_EXCEPTION_SPECIF
* _Unwind_Exception structure within this structure, and should be passed to
* the C++ personality function.
*/
__attribute__((weak))
OBJC_WEAK
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These don't actually need to be weak anymore. We don't support a separate libobjcxx.so anymore, so we know at compile time whether these are present. We should remove the weak attribute here and make sure we're not doing dynamic checks for their existence anywhere.


if (HAVE_LIBSTDCXX)
find_library(CXX_RUNTIME "stdc++" REQUIRED)
set(CXX_RUNTIME_NAME "libstdc++-6")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard coding the libstdc++ version seems very fragile.

find_library(CXX_RUNTIME "c++" REQUIRED)
get_filename_component(CXX_RUNTIME_NAME ${CXX_RUNTIME} NAME_WE CACHE)
else ()
message(WARNING "Could not determine the C++ runtime.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is detecting the C++ standard library, not the runtime. Does MinGW guarantee that the latter is always accessed via the former? This is not true on FreeBSD, for example, where libc++.so is a linker script that links libc++ and libcxxrt, so that we could ship libstdc++ and libc++ with a common runtime.

@qmfrederik
Copy link
Collaborator Author

Let's do it properly - #280

@qmfrederik qmfrederik closed this Feb 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants