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

[libc++] Fix broken configuration system-libcxxabi on Apple #110920

Merged
merged 2 commits into from
Oct 9, 2024

Conversation

ldionne
Copy link
Member

@ldionne ldionne commented Oct 2, 2024

On Apple platforms, using system-libcxxabi as an ABI library wouldn't work because we'd try to re-export symbols from libc++abi that the system libc++abi.dylib might not have. Instead, only re-export those symbols when we're using the in-tree libc++abi.

This does mean that libc++.dylib won't re-export any libc++abi symbols when building against the system libc++abi, which could be fixed in various ways. However, the best solution really depends on the intended use case, so this patch doesn't try to solve that problem.

As a drive-by, also improve the diagnostic message when the user forgets to set the LIBCXX_CXX_ABI_INCLUDE_PATHS variable, which would previously lead to a confusing error.

Closes #104672

On Apple platforms, using system-libcxxabi as an ABI library wouldn't
work because we'd try to re-export symbols from libc++abi that the
system libc++abi.dylib might not have. Instead, only re-export those
symbols when we're using the in-tree libc++abi.

This does mean that libc++.dylib won't re-export any libc++abi symbols
when building against the system libc++abi, which could be fixed in
various ways. However, the best solution really depends on the intended
use case, so this patch doesn't try to solve that problem.

As a drive-by, also improve the diagnostic message when the user forgets
to set the LIBCXX_CXX_ABI_INCLUDE_PATHS variable, which would previously
lead to a confusing error.

Closes llvm#104672
@ldionne ldionne requested review from a team as code owners October 2, 2024 19:54
@llvmbot llvmbot added libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. libc++abi libc++abi C++ Runtime Library. Not libc++. labels Oct 2, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Oct 2, 2024

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

Changes

On Apple platforms, using system-libcxxabi as an ABI library wouldn't work because we'd try to re-export symbols from libc++abi that the system libc++abi.dylib might not have. Instead, only re-export those symbols when we're using the in-tree libc++abi.

This does mean that libc++.dylib won't re-export any libc++abi symbols when building against the system libc++abi, which could be fixed in various ways. However, the best solution really depends on the intended use case, so this patch doesn't try to solve that problem.

As a drive-by, also improve the diagnostic message when the user forgets to set the LIBCXX_CXX_ABI_INCLUDE_PATHS variable, which would previously lead to a confusing error.

Closes #104672


Full diff: https://github.com/llvm/llvm-project/pull/110920.diff

3 Files Affected:

  • (modified) libcxx/cmake/Modules/HandleLibCXXABI.cmake (+19-1)
  • (modified) libcxx/src/CMakeLists.txt (+2-6)
  • (modified) libcxxabi/src/CMakeLists.txt (+2)
diff --git a/libcxx/cmake/Modules/HandleLibCXXABI.cmake b/libcxx/cmake/Modules/HandleLibCXXABI.cmake
index 34e9a672a960f9..e7caf3044b8bcf 100644
--- a/libcxx/cmake/Modules/HandleLibCXXABI.cmake
+++ b/libcxx/cmake/Modules/HandleLibCXXABI.cmake
@@ -83,6 +83,10 @@ endfunction()
 
 # Link against a system-provided libstdc++
 if ("${LIBCXX_CXX_ABI}" STREQUAL "libstdc++")
+  if(NOT LIBCXX_CXX_ABI_INCLUDE_PATHS)
+    message(FATAL_ERROR "LIBCXX_CXX_ABI_INCLUDE_PATHS must be set when selecting libstdc++ as an ABI library")
+  endif()
+
   add_library(libcxx-abi-headers INTERFACE)
   import_private_headers(libcxx-abi-headers "${LIBCXX_CXX_ABI_INCLUDE_PATHS}"
     "cxxabi.h;bits/c++config.h;bits/os_defines.h;bits/cpu_defines.h;bits/cxxabi_tweaks.h;bits/cxxabi_forced.h")
@@ -96,6 +100,10 @@ if ("${LIBCXX_CXX_ABI}" STREQUAL "libstdc++")
 
 # Link against a system-provided libsupc++
 elseif ("${LIBCXX_CXX_ABI}" STREQUAL "libsupc++")
+  if(NOT LIBCXX_CXX_ABI_INCLUDE_PATHS)
+    message(FATAL_ERROR "LIBCXX_CXX_ABI_INCLUDE_PATHS must be set when selecting libsupc++ as an ABI library")
+  endif()
+
   add_library(libcxx-abi-headers INTERFACE)
   import_private_headers(libcxx-abi-headers "${LIBCXX_CXX_ABI_INCLUDE_PATHS}"
     "cxxabi.h;bits/c++config.h;bits/os_defines.h;bits/cpu_defines.h;bits/cxxabi_tweaks.h;bits/cxxabi_forced.h")
@@ -114,7 +122,13 @@ elseif ("${LIBCXX_CXX_ABI}" STREQUAL "libcxxabi")
   target_compile_definitions(libcxx-abi-headers INTERFACE "-DLIBCXX_BUILDING_LIBCXXABI")
 
   if (TARGET cxxabi_shared)
-    add_library(libcxx-abi-shared ALIAS cxxabi_shared)
+    add_library(libcxx-abi-shared INTERFACE)
+    target_link_libraries(libcxx-abi-shared INTERFACE cxxabi_shared)
+
+    # When using the in-tree libc++abi as an ABI library, libc++ re-exports the
+    # libc++abi symbols (on platforms where it can) because libc++abi is only an
+    # implementation detail of libc++.
+    target_link_libraries(libcxx-abi-shared INTERFACE cxxabi-reexports)
   endif()
 
   if (TARGET cxxabi_static)
@@ -131,6 +145,10 @@ elseif ("${LIBCXX_CXX_ABI}" STREQUAL "libcxxabi")
 
 # Link against a system-provided libc++abi
 elseif ("${LIBCXX_CXX_ABI}" STREQUAL "system-libcxxabi")
+  if(NOT LIBCXX_CXX_ABI_INCLUDE_PATHS)
+    message(FATAL_ERROR "LIBCXX_CXX_ABI_INCLUDE_PATHS must be set when selecting system-libcxxabi as an ABI library")
+  endif()
+
   add_library(libcxx-abi-headers INTERFACE)
   import_private_headers(libcxx-abi-headers "${LIBCXX_CXX_ABI_INCLUDE_PATHS}" "cxxabi.h;__cxxabi_config.h")
   target_compile_definitions(libcxx-abi-headers INTERFACE "-DLIBCXX_BUILDING_LIBCXXABI")
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 48c5111a0acbf6..6b928ebeeeb221 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -210,14 +210,10 @@ if (LIBCXX_ENABLE_SHARED)
     target_link_libraries(cxx_shared PUBLIC libcxx-abi-shared)
   endif()
 
-  # Maybe re-export symbols from libc++abi
-  # In particular, we don't re-export the symbols if libc++abi is merged statically
-  # into libc++ because in that case there's no dylib to re-export from.
+  # Maybe force some symbols to be weak, not weak or not exported.
+  # TODO: This shouldn't depend on the platform, and ideally it should be done in the sources.
   if (APPLE AND LIBCXX_CXX_ABI MATCHES "libcxxabi$"
             AND NOT LIBCXX_STATICALLY_LINK_ABI_IN_SHARED_LIBRARY)
-    target_link_libraries(cxx_shared PRIVATE cxxabi-reexports)
-
-    # TODO: These exports controls should not be tied to whether we re-export libc++abi symbols
     target_link_libraries(cxx_shared PRIVATE
       "-Wl,-unexported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/../lib/libc++unexp.exp"
       "-Wl,-force_symbols_not_weak_list,${CMAKE_CURRENT_SOURCE_DIR}/../lib/notweak.exp"
diff --git a/libcxxabi/src/CMakeLists.txt b/libcxxabi/src/CMakeLists.txt
index 6f16c614212ef1..480e528b819bb9 100644
--- a/libcxxabi/src/CMakeLists.txt
+++ b/libcxxabi/src/CMakeLists.txt
@@ -213,6 +213,8 @@ if (LIBCXXABI_ENABLE_SHARED)
     list(APPEND LIBCXXABI_INSTALL_TARGETS "cxxabi_shared")
   endif()
 
+  # TODO: Move this to libc++'s HandleLibCXXABI.cmake since this is effectively trying to control
+  #       what libc++ re-exports.
   add_library(cxxabi-reexports INTERFACE)
   function(export_symbols file)
     # -exported_symbols_list is only available on Apple platforms

@llvmbot
Copy link
Collaborator

llvmbot commented Oct 2, 2024

@llvm/pr-subscribers-libcxxabi

Author: Louis Dionne (ldionne)

Changes

On Apple platforms, using system-libcxxabi as an ABI library wouldn't work because we'd try to re-export symbols from libc++abi that the system libc++abi.dylib might not have. Instead, only re-export those symbols when we're using the in-tree libc++abi.

This does mean that libc++.dylib won't re-export any libc++abi symbols when building against the system libc++abi, which could be fixed in various ways. However, the best solution really depends on the intended use case, so this patch doesn't try to solve that problem.

As a drive-by, also improve the diagnostic message when the user forgets to set the LIBCXX_CXX_ABI_INCLUDE_PATHS variable, which would previously lead to a confusing error.

Closes #104672


Full diff: https://github.com/llvm/llvm-project/pull/110920.diff

3 Files Affected:

  • (modified) libcxx/cmake/Modules/HandleLibCXXABI.cmake (+19-1)
  • (modified) libcxx/src/CMakeLists.txt (+2-6)
  • (modified) libcxxabi/src/CMakeLists.txt (+2)
diff --git a/libcxx/cmake/Modules/HandleLibCXXABI.cmake b/libcxx/cmake/Modules/HandleLibCXXABI.cmake
index 34e9a672a960f9..e7caf3044b8bcf 100644
--- a/libcxx/cmake/Modules/HandleLibCXXABI.cmake
+++ b/libcxx/cmake/Modules/HandleLibCXXABI.cmake
@@ -83,6 +83,10 @@ endfunction()
 
 # Link against a system-provided libstdc++
 if ("${LIBCXX_CXX_ABI}" STREQUAL "libstdc++")
+  if(NOT LIBCXX_CXX_ABI_INCLUDE_PATHS)
+    message(FATAL_ERROR "LIBCXX_CXX_ABI_INCLUDE_PATHS must be set when selecting libstdc++ as an ABI library")
+  endif()
+
   add_library(libcxx-abi-headers INTERFACE)
   import_private_headers(libcxx-abi-headers "${LIBCXX_CXX_ABI_INCLUDE_PATHS}"
     "cxxabi.h;bits/c++config.h;bits/os_defines.h;bits/cpu_defines.h;bits/cxxabi_tweaks.h;bits/cxxabi_forced.h")
@@ -96,6 +100,10 @@ if ("${LIBCXX_CXX_ABI}" STREQUAL "libstdc++")
 
 # Link against a system-provided libsupc++
 elseif ("${LIBCXX_CXX_ABI}" STREQUAL "libsupc++")
+  if(NOT LIBCXX_CXX_ABI_INCLUDE_PATHS)
+    message(FATAL_ERROR "LIBCXX_CXX_ABI_INCLUDE_PATHS must be set when selecting libsupc++ as an ABI library")
+  endif()
+
   add_library(libcxx-abi-headers INTERFACE)
   import_private_headers(libcxx-abi-headers "${LIBCXX_CXX_ABI_INCLUDE_PATHS}"
     "cxxabi.h;bits/c++config.h;bits/os_defines.h;bits/cpu_defines.h;bits/cxxabi_tweaks.h;bits/cxxabi_forced.h")
@@ -114,7 +122,13 @@ elseif ("${LIBCXX_CXX_ABI}" STREQUAL "libcxxabi")
   target_compile_definitions(libcxx-abi-headers INTERFACE "-DLIBCXX_BUILDING_LIBCXXABI")
 
   if (TARGET cxxabi_shared)
-    add_library(libcxx-abi-shared ALIAS cxxabi_shared)
+    add_library(libcxx-abi-shared INTERFACE)
+    target_link_libraries(libcxx-abi-shared INTERFACE cxxabi_shared)
+
+    # When using the in-tree libc++abi as an ABI library, libc++ re-exports the
+    # libc++abi symbols (on platforms where it can) because libc++abi is only an
+    # implementation detail of libc++.
+    target_link_libraries(libcxx-abi-shared INTERFACE cxxabi-reexports)
   endif()
 
   if (TARGET cxxabi_static)
@@ -131,6 +145,10 @@ elseif ("${LIBCXX_CXX_ABI}" STREQUAL "libcxxabi")
 
 # Link against a system-provided libc++abi
 elseif ("${LIBCXX_CXX_ABI}" STREQUAL "system-libcxxabi")
+  if(NOT LIBCXX_CXX_ABI_INCLUDE_PATHS)
+    message(FATAL_ERROR "LIBCXX_CXX_ABI_INCLUDE_PATHS must be set when selecting system-libcxxabi as an ABI library")
+  endif()
+
   add_library(libcxx-abi-headers INTERFACE)
   import_private_headers(libcxx-abi-headers "${LIBCXX_CXX_ABI_INCLUDE_PATHS}" "cxxabi.h;__cxxabi_config.h")
   target_compile_definitions(libcxx-abi-headers INTERFACE "-DLIBCXX_BUILDING_LIBCXXABI")
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 48c5111a0acbf6..6b928ebeeeb221 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -210,14 +210,10 @@ if (LIBCXX_ENABLE_SHARED)
     target_link_libraries(cxx_shared PUBLIC libcxx-abi-shared)
   endif()
 
-  # Maybe re-export symbols from libc++abi
-  # In particular, we don't re-export the symbols if libc++abi is merged statically
-  # into libc++ because in that case there's no dylib to re-export from.
+  # Maybe force some symbols to be weak, not weak or not exported.
+  # TODO: This shouldn't depend on the platform, and ideally it should be done in the sources.
   if (APPLE AND LIBCXX_CXX_ABI MATCHES "libcxxabi$"
             AND NOT LIBCXX_STATICALLY_LINK_ABI_IN_SHARED_LIBRARY)
-    target_link_libraries(cxx_shared PRIVATE cxxabi-reexports)
-
-    # TODO: These exports controls should not be tied to whether we re-export libc++abi symbols
     target_link_libraries(cxx_shared PRIVATE
       "-Wl,-unexported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/../lib/libc++unexp.exp"
       "-Wl,-force_symbols_not_weak_list,${CMAKE_CURRENT_SOURCE_DIR}/../lib/notweak.exp"
diff --git a/libcxxabi/src/CMakeLists.txt b/libcxxabi/src/CMakeLists.txt
index 6f16c614212ef1..480e528b819bb9 100644
--- a/libcxxabi/src/CMakeLists.txt
+++ b/libcxxabi/src/CMakeLists.txt
@@ -213,6 +213,8 @@ if (LIBCXXABI_ENABLE_SHARED)
     list(APPEND LIBCXXABI_INSTALL_TARGETS "cxxabi_shared")
   endif()
 
+  # TODO: Move this to libc++'s HandleLibCXXABI.cmake since this is effectively trying to control
+  #       what libc++ re-exports.
   add_library(cxxabi-reexports INTERFACE)
   function(export_symbols file)
     # -exported_symbols_list is only available on Apple platforms

@ldionne
Copy link
Member Author

ldionne commented Oct 3, 2024

@h-vetinari It would be great if you can let me know whether this solves your problem

@h-vetinari
Copy link
Contributor

@h-vetinari It would be great if you can let me know whether this solves your problem

Thanks a lot for this, I'll test this and get back to you! 🙏

@h-vetinari
Copy link
Contributor

So I'm trying this in conda-forge/libcxx-feedstock#177, and I'm getting

+ llvm-nm $PREFIX/lib/libc++.1.dylib
dyld[11925]: Symbol not found: __ZTVN10__cxxabiv117__class_type_infoE
  Referenced from: <5D911EBA-4AB2-3C92-B9D5-977E209853B5> $PREFIX/bin/llvm-nm-18
  Expected in:     <E516FB63-086F-3A12-B5C7-A0FD4F00B3EA> $PREFIX/lib/libc++.1.0.dylib
compile_test.sh: line 12: 11925 Abort trap: 6           llvm-nm $PREFIX/lib/libc++.1.dylib

It's plausible/possible that this is a behavioural difference between our current workaround to depend on the system libcxxabi and doing it "properly". In particular, the llvm-nm binary that's in use for testing after successful compilation was built1 against the "current world" of our libcxx handling, which does roughly:

ninja -C build cxx cxxabi unwind
ninja -C build install-cxx install-cxxabi install-unwind
$INSTALL_NAME_TOOL -change "@rpath/libc++abi.1.dylib" "/usr/lib/libc++abi.dylib" $PREFIX/lib/libc++.1.0.dylib

I'm aware that this is fragile (and we do carry a patch for systems before macOS 12 due to ABI differences), but it's what we had to move to, because the system libcxxabi gets picked up in some situations (e.g. from CPython, see README here).

IOW, it's conceivable to me that (re)building everything in a -DLIBCXX_CXX_ABI=system-libcxxabi world would work out fine. For now though I'm not sure how a cxxabi symbol ends up in libc++.1.0.dylib (using the script above) but we'd have to fix that somehow to ensure stuff doesn't break if/when we switch.

CC @isuruf

Footnotes

  1. For various reasons we do rely on shared libraries wherever possible. Meaning that llvm-nm itself depends on libc++, and when we build a new libc++, the existing builds of llvm-nm should keep working seamlessly if their libc++ gets updated underneath them. This is where we're now falling over, i.e. it'd be a kind of ABI break for us -- assuming this isn't due to a bug somehow.

@h-vetinari
Copy link
Contributor

OK, rereading your comment from #104672

However, note that even when you do that, now the build is going to start trying to re-export a list of libc++abi symbols from libc++, and obviously that only works if your system ABI library is sufficiently recent to have all of those symbols. It's kind of a brittle setup.

now makes more sense. The thing I don't yet get is how things are working for us in the current setup; presumably the fact that the libcxxabi symbols don't get re-exported through our workaround helps.

@ldionne
Copy link
Member Author

ldionne commented Oct 7, 2024

So I'm trying this in conda-forge/libcxx-feedstock#177, and I'm getting

+ llvm-nm $PREFIX/lib/libc++.1.dylib
dyld[11925]: Symbol not found: __ZTVN10__cxxabiv117__class_type_infoE
  Referenced from: <5D911EBA-4AB2-3C92-B9D5-977E209853B5> $PREFIX/bin/llvm-nm-18
  Expected in:     <E516FB63-086F-3A12-B5C7-A0FD4F00B3EA> $PREFIX/lib/libc++.1.0.dylib
compile_test.sh: line 12: 11925 Abort trap: 6           llvm-nm $PREFIX/lib/libc++.1.dylib

How was $PREFIX/lib/libc++.1.0.dylib built? Presumably it was built in a way that it advertised the presence of symbol __ZTVN10__cxxabiv117__class_type_infoE, which is the only way the linker would have encoded that dependency in llvm-nm-18.


Another thing you could try on the side is to re-export libc++abi.dylib itself from libc++.dylib instead of the list of its symbols. There's a fundamental problem with re-exporting the list of libc++abi symbols that otherwise can't be solved when building against the system libc++abi: we don't know what version of the system libc++abi you're building against, so we don't know the list of symbols. And even if we knew the version of libc++abi you're building against, we'd have to maintain a mapping of every historical libc++abi.dylib that was shipped to the list of symbols it contained in order to make that happen.

@ldionne
Copy link
Member Author

ldionne commented Oct 9, 2024

I'm going to ship this since it does improve some of the story for this configuration, even though the question of how to properly re-export symbols from the system libc++abi isn't resolved yet. Shipping this will unblock my work on another patch which cleans up the ABI selection mechanism a lot more.

@ldionne ldionne merged commit 21da4e7 into llvm:main Oct 9, 2024
67 checks passed
@ldionne ldionne deleted the review/fix-broken-system-libcxxabi branch October 9, 2024 12:47
@h-vetinari
Copy link
Contributor

Sorry for the delayed response! I was planning to get to this but not fast enough apparently 😅

There's a fundamental problem with re-exporting the list of libc++abi symbols that otherwise can't be solved when building against the system libc++abi: we don't know what version of the system libc++abi you're building against, so we don't know the list of symbols.

Not sure I understand 100%, but in our case the system ABI has a clear lower bound and we know it at build-time (currently 10.13 on osx-64, 11.0 on osx-arm64).

And even if we knew the version of libc++abi you're building against, we'd have to maintain a mapping of every historical libc++abi.dylib that was shipped to the list of symbols it contained in order to make that happen.

This might be naïve, but can't we just start by relying on the availability of symbols from the lower-bound system ABI? This is effectively what we're doing already. If we're building on 10.13, we require (through our package metadata that drives whether an artefact can be installed) that the resulting package can only be installed on macOS>=10.13. That has worked for a long time already, and didn't need a full mapping.

How was $PREFIX/lib/libc++.1.0.dylib built?

I had pasted the essence of the build script above; here's the link to the full thing again.

Presumably it was built in a way that it advertised the presence of symbol __ZTVN10__cxxabiv117__class_type_infoE, which is the only way the linker would have encoded that dependency in llvm-nm-18.

That sounds very plausible, but it needs to be happening essentially by default, because I'm not actively choosing to re-export the libcxxabi symbols (I would have wanted to avoid that, had I known).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++abi libc++abi C++ Runtime Library. Not libc++. libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[libc++] -DLIBCXX_CXX_ABI=system-libcxxabi not usable?
3 participants