Hi,
another common source of unnecesary throw_bad_alloc calls is 
basic_string::_M_create.
    basic_string<_CharT, _Traits, _Alloc>::
    _M_create(size_type& __capacity, size_type __old_capacity)
    {
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // 83.  String::npos vs. string::max_size()
      if (__capacity > max_size())
        std::__throw_length_error(__N("basic_string::_M_create"));

      // The below implements an exponential growth policy, necessary to
      // meet amortized linear time requirements of the library: see
      // http://gcc.gnu.org/ml/libstdc++/2001-07/msg00085.html.
      if (__capacity > __old_capacity && __capacity < 2 * __old_capacity)
        {
          __capacity = 2 * __old_capacity;
          // Never allocate a string bigger than max_size.
          if (__capacity > max_size())
            __capacity = max_size();
        }

      // NB: Need an array of char_type[__capacity], plus a terminating
      // null char_type() element.
      return _S_allocate(_M_get_allocator(), __capacity + 1);
    }

The code makes it visible that string is never bigger then max_size, however + 1
makes it one byte bigger than maximal size allowed by allocator.  I believe
this is by miscounting the 0 at the end of string in max_size function:

-      { return (_Alloc_traits::max_size(_M_get_allocator()) - 1) / 2; }
+      { return _Alloc_traits::max_size(_M_get_allocator()) / 2 - 1; }

Path also adds __builtin_unreachable to express value ranges of capacity and 
length.
This makes it possible to optimize out throw when copying strings as in the 
testcase.

std::string
test (std::string &a)
{
        return a;
}

This now yields the following:

struct string test (struct string & a)
{
  size_type __siz;
  struct string & _2(D);
  char[16] * _4;
  char * _5;
  char * _7;
  char * prephitmp_10;
  char * pretmp_11;
  char _15;
  char * _18;
  char * _27;
  long unsigned int _32;

  <bb 2> [local count: 1073741824]:
  _4 = &MEM[(struct basic_string *)_2(D)].D.32970._M_local_buf;
  MEM[(struct _Alloc_hider *)_2(D)]._M_p = _4;
  _5 = MEM[(const struct basic_string *)a_3(D)]._M_dataplus._M_p;
  __siz_6 = MEM[(const struct basic_string *)a_3(D)]._M_string_length;
  if (__siz_6 > 15)
    goto <bb 3>; [33.00%]
  else
    goto <bb 4>; [67.00%]

  <bb 3> [local count: 354334800]:
  _32 = __siz_6 + 1;
  _27 = operator new (_32);
  MEM[(struct basic_string *)_2(D)]._M_dataplus._M_p = _27;
  MEM[(struct basic_string *)_2(D)].D.32970._M_allocated_capacity = __siz_6;
  goto <bb 8>; [100.00%]

  <bb 4> [local count: 719407024]:
  if (__siz_6 == 1)
    goto <bb 5>; [34.00%]
  else
    goto <bb 7>; [66.00%]

  <bb 5> [local count: 353024841]:
  _15 = MEM[(const char_type &)_5];
  MEM[(char_type &)_2(D) + 16] = _15;

  <bb 6> [local count: 350316931]:
  goto <bb 9>; [100.00%]

  <bb 7> [local count: 474808633]:
  if (__siz_6 == 0)
    goto <bb 6>; [73.78%]
  else
    goto <bb 8>; [26.22%]

  <bb 8> [local count: 334966574]:
  # _7 = PHI <_4(7), _27(3)>
  __builtin_memcpy (_7, _5, __siz_6);
  pretmp_11 = MEM[(const struct basic_string *)_2(D)]._M_dataplus._M_p;

  <bb 9> [local count: 1038308344]:
  # prephitmp_10 = PHI <_4(6), pretmp_11(8)>
  MEM[(struct basic_string *)_2(D)]._M_string_length = __siz_6;
  _18 = prephitmp_10 + __siz_6;
  MEM[(char_type &)_18] = 0;
  return _2(D);

}

.text segment is reduced from 213 bytes to 192 (saving 2 conditionals
and 2 calls to throw)

I find it funny that the code special cases a.size () == 1 to copy single byte
and a.size () == 0 to bypass memcpy call.  I would say it is job of middle-end
to optimize memcpy reasonably even for small blocks.  Was this based on some 
data
showing that many strings have size of 0 and 1 which is not visible to
compiler?

regtested x86_64-linux, OK?

Honza

libstdc++-v3/ChangeLog:

        * include/bits/basic_string.h:

gcc/testsuite/ChangeLog:

        * g++.dg/tree-ssa/string-1.C: New test.

diff --git a/gcc/testsuite/g++.dg/tree-ssa/string-1.C 
b/gcc/testsuite/g++.dg/tree-ssa/string-1.C
new file mode 100644
index 00000000000..d38c23a7628
--- /dev/null
+++ b/gcc/testsuite/g++.dg/tree-ssa/string-1.C
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -std=c++20 -fdump-tree-optimized" } */
+#include <string>
+std::string
+test (std::string &a)
+{
+       return a;
+}
+/* { dg-final { scan-tree-dump-not "throw" "optimized" } } */
diff --git a/libstdc++-v3/include/bits/basic_string.h 
b/libstdc++-v3/include/bits/basic_string.h
index f5b320099b1..d754deb28ed 100644
--- a/libstdc++-v3/include/bits/basic_string.h
+++ b/libstdc++-v3/include/bits/basic_string.h
@@ -1079,20 +1079,30 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
       _GLIBCXX_NODISCARD _GLIBCXX20_CONSTEXPR
       size_type
       size() const _GLIBCXX_NOEXCEPT
-      { return _M_string_length; }
+      {
+       size_type __siz = _M_string_length;
+       if (__siz > max_size ())
+         __builtin_unreachable ();
+       return __siz;
+      }
 
       ///  Returns the number of characters in the string, not including any
       ///  null-termination.
       _GLIBCXX_NODISCARD _GLIBCXX20_CONSTEXPR
       size_type
       length() const _GLIBCXX_NOEXCEPT
-      { return _M_string_length; }
+      {
+       size_type __siz = _M_string_length;
+       if (__siz > max_size ())
+         __builtin_unreachable ();
+       return __siz;
+      }
 
       ///  Returns the size() of the largest possible %string.
       _GLIBCXX_NODISCARD _GLIBCXX20_CONSTEXPR
       size_type
       max_size() const _GLIBCXX_NOEXCEPT
-      { return (_Alloc_traits::max_size(_M_get_allocator()) - 1) / 2; }
+      { return _Alloc_traits::max_size(_M_get_allocator()) / 2 - 1; }
 
       /**
        *  @brief  Resizes the %string to the specified number of characters.
@@ -1184,8 +1194,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
       size_type
       capacity() const _GLIBCXX_NOEXCEPT
       {
-       return _M_is_local() ? size_type(_S_local_capacity)
-                            : _M_allocated_capacity;
+       size_t __siz = _M_is_local() ? size_type(_S_local_capacity)
+                                    : _M_allocated_capacity;
+       if (__siz < _S_local_capacity || __siz > max_size () + 1)
+         __builtin_unreachable ();
+       return __siz;
       }
 
       /**

Reply via email to