[libcxx-commits] [libcxx] 84b0b52 - [libc++] Refactor how basic_string and vector hoist exception-throwing functions

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 5 17:53:53 PDT 2021


Author: Louis Dionne
Date: 2021-10-05T20:53:40-04:00
New Revision: 84b0b52b036cc1fbe2b88038fd700e83b7b67055

URL: https://github.com/llvm/llvm-project/commit/84b0b52b036cc1fbe2b88038fd700e83b7b67055
DIFF: https://github.com/llvm/llvm-project/commit/84b0b52b036cc1fbe2b88038fd700e83b7b67055.diff

LOG: [libc++] Refactor how basic_string and vector hoist exception-throwing functions

In basic_string and vector, we've been encapsulating all exception
throwing code paths in helper functions of a base class, which are defined
in the compiled library. For example, __vector_base_common defines two
methods, __throw_length_error() and __throw_out_of_range(), and the class
is externally instantiated in the library. This was done a long time ago,
but after investigating, I believe the goal of the current design was to:

1. Encapsulate the code to throw an exception (which is non-trivial) in
   an externally-defined function so that the important code paths that
   call it (e.g. vector::at) are free from that code. Basically, the
   intent is for the "hot" code path to contain a single conditional jump
   (based on checking the error condition) to an externally-defined function,
   which handles all the exception-throwing business.

2. Avoid defining this exception-throwing function once per instantiation
   of the class template. In other words, we want a single copy of
   __throw_length_error even if we have vector<int>, vector<char>, etc.

3. Encapsulate the passing of the container-specific string (i.e. "vector"
   and "basic_string") to the underlying exception-throwing function
   so that object files don't contain those duplicated string literals.
   For example, we'd like to have a single "vector" string literal for
   passing to `std::__throw_length_error` in the library, instead of
   having one per translation unit.

However, the way this is achieved right now has two problems:

- Using a base class and exporting it is really weird - I've been confused
  about this ever since I first saw it. It's just a really unusual way of
  achieving the above goals. Also, it's made even worse by the fact that
  the definitions of __throw_length_error and __throw_out_of_range appear
  in the headers despite always being intended to be defined in the compiled
  library (via the extern template instantiation).

- We end up exporting those functions as weak symbols, which isn't great
  for load times. Instead, it would be better to export those as strong
  symbols from the library.

This patch fixes those issues while retaining ABI compatibility (e.g. we
still export the exact same symbols as before). Note that we need to
keep the base classes as-is to avoid breaking the ABI of someone who
might inherit from std::basic_string or std::vector.

Differential Revision: https://reviews.llvm.org/D111173

Added: 
    

Modified: 
    libcxx/include/string
    libcxx/include/vector
    libcxx/src/string.cpp
    libcxx/src/vector.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/include/string b/libcxx/include/string
index 87e66b68497a..c6a799dbc7e8 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -613,28 +613,14 @@ operator+(const basic_string<_CharT, _Traits, _Allocator>& __x, _CharT __y);
 _LIBCPP_EXTERN_TEMPLATE(_LIBCPP_FUNC_VIS string operator+<char, char_traits<char>, allocator<char> >(char const*, string const&))
 
 template <bool>
-class _LIBCPP_TEMPLATE_VIS __basic_string_common
-{
-protected:
-    _LIBCPP_NORETURN void __throw_length_error() const;
-    _LIBCPP_NORETURN void __throw_out_of_range() const;
-};
-
-template <bool __b>
-void
-__basic_string_common<__b>::__throw_length_error() const
-{
-    _VSTD::__throw_length_error("basic_string");
-}
+struct __basic_string_common;
 
-template <bool __b>
-void
-__basic_string_common<__b>::__throw_out_of_range() const
-{
-    _VSTD::__throw_out_of_range("basic_string");
-}
-
-_LIBCPP_EXTERN_TEMPLATE(class _LIBCPP_EXTERN_TEMPLATE_TYPE_VIS __basic_string_common<true>)
+template <>
+struct __basic_string_common<true> {
+    // Both are defined in string.cpp
+    _LIBCPP_NORETURN _LIBCPP_EXPORTED_FROM_ABI void __throw_length_error() const;
+    _LIBCPP_NORETURN _LIBCPP_EXPORTED_FROM_ABI void __throw_out_of_range() const;
+};
 
 template <class _Iter>
 struct __string_is_trivial_iterator : public false_type {};
@@ -688,7 +674,7 @@ class
     _LIBCPP_PREFERRED_NAME(u32string)
 #endif
     basic_string
-    : private __basic_string_common<true>
+    : private __basic_string_common<true> // This base class is historical, but it needs to remain for ABI compatibility
 {
 public:
     typedef basic_string                                 __self;

diff  --git a/libcxx/include/vector b/libcxx/include/vector
index c2deb5bb3a7e..626c917f3a6d 100644
--- a/libcxx/include/vector
+++ b/libcxx/include/vector
@@ -302,33 +302,18 @@ _LIBCPP_PUSH_MACROS
 _LIBCPP_BEGIN_NAMESPACE_STD
 
 template <bool>
-class _LIBCPP_TEMPLATE_VIS __vector_base_common
-{
-protected:
-    _LIBCPP_INLINE_VISIBILITY __vector_base_common() {}
-    _LIBCPP_NORETURN void __throw_length_error() const;
-    _LIBCPP_NORETURN void __throw_out_of_range() const;
-};
-
-template <bool __b>
-void
-__vector_base_common<__b>::__throw_length_error() const
-{
-    _VSTD::__throw_length_error("vector");
-}
-
-template <bool __b>
-void
-__vector_base_common<__b>::__throw_out_of_range() const
-{
-    _VSTD::__throw_out_of_range("vector");
-}
+struct __vector_base_common;
 
-_LIBCPP_EXTERN_TEMPLATE(class _LIBCPP_EXTERN_TEMPLATE_TYPE_VIS __vector_base_common<true>)
+template <>
+struct __vector_base_common<true> {
+    // Both are defined in vector.cpp
+    _LIBCPP_NORETURN _LIBCPP_EXPORTED_FROM_ABI void __throw_length_error() const;
+    _LIBCPP_NORETURN _LIBCPP_EXPORTED_FROM_ABI void __throw_out_of_range() const;
+};
 
 template <class _Tp, class _Allocator>
 class __vector_base
-    : protected __vector_base_common<true>
+    : protected __vector_base_common<true> // This base class is historical, but it needs to remain for ABI compatibility
 {
 public:
     typedef _Allocator                               allocator_type;

diff  --git a/libcxx/src/string.cpp b/libcxx/src/string.cpp
index 97a773f79a3b..012b2ea08958 100644
--- a/libcxx/src/string.cpp
+++ b/libcxx/src/string.cpp
@@ -18,7 +18,13 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template class _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS __basic_string_common<true>;
+void __basic_string_common<true>::__throw_length_error() const {
+    _VSTD::__throw_length_error("basic_string");
+}
+
+void __basic_string_common<true>::__throw_out_of_range() const {
+    _VSTD::__throw_out_of_range("basic_string");
+}
 
 #define _LIBCPP_EXTERN_TEMPLATE_DEFINE(...) template __VA_ARGS__;
 #ifdef _LIBCPP_ABI_STRING_OPTIMIZED_EXTERNAL_INSTANTIATION

diff  --git a/libcxx/src/vector.cpp b/libcxx/src/vector.cpp
index 3b65e558fd0a..0570ede733c3 100644
--- a/libcxx/src/vector.cpp
+++ b/libcxx/src/vector.cpp
@@ -10,6 +10,12 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template class _LIBCPP_CLASS_TEMPLATE_INSTANTIATION_VIS __vector_base_common<true>;
+void __vector_base_common<true>::__throw_length_error() const {
+    _VSTD::__throw_length_error("vector");
+}
+
+void __vector_base_common<true>::__throw_out_of_range() const {
+    _VSTD::__throw_out_of_range("vector");
+}
 
 _LIBCPP_END_NAMESPACE_STD


        


More information about the libcxx-commits mailing list