[libcxx-commits] [PATCH] D123580: [libc++] Use bit field for checking if string is in long or short mode

Louis Dionne via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Wed Apr 13 09:35:24 PDT 2022


ldionne requested changes to this revision.
ldionne added a subscriber: saugustine.
ldionne added a comment.
This revision now requires changes to proceed.

I really like this cleanup, I think it makes it much easier to understand what's going on. I do have some comments, but this is looking pretty good.



================
Comment at: libcxx/include/string:1486
+        if (__s > 127)
+            __libcpp_unreachable();
+        __r_.first().__s.__size_ = __s;
----------------
philnik wrote:
> Mordante wrote:
> > philnik wrote:
> > > Mordante wrote:
> > > > Mainly curious, but why not a `_LIBCPP_ASSERT`?
> > > This ensures optimal code generation. GCC doesn't have `__builtin_assume`. This is a workaround. 
> > From a maintainability PoV I like `_LIBCPP_ASSERT` better. Would it be an option to adjust `_LIBCPP_ASSERT?` the "generate" this code when using GCC?
> I agree that `_LIBCPP_ASSERT` would be nicer to read. The problem is that using this trick for `_LIBCPP_ASSERT` could lead to extra code generation in more complex scenarios. We would have to introduce something else like `_LIBCPP_ASSUME`, which should only be used for trivial cases. But for two instances I don't think this is justified.
Can you try again with `_LIBCPP_ASSERT` by making `__libcpp_assertion_handler` `noreturn`? This is how GCC implements their own `assert(...)`, so presumably there might be some optimization opportunities for them there. I think this would be much better than using `unreachable()`.

Edit: Yuck, this indeed won't help when assertions are disabled on GCC. However, looking at the codegen, it appears that the only difference is that the compiler adds a single instruction to ensure that `__s <= 127`. Unless you can show us a case where there is a significant difference, I think the way to go is to use `_LIBCPP_ASSERT` and for GCC to be able to pick it up.


================
Comment at: libcxx/include/string:695-700
+// The __endian_factor is required, because the size_type has now one bit less space.
+// If the LSB is used in short string mode we have to multiply the size by two when the it is stored and
+// divide by two when it is loaded. In long string mode we ignore it,
+// because we can assume that we always allocate an even amount of value_types.
+// If the MSB is used the max_size() is numeric_limits<size_type>::max() / 2.
+// If the factor is 2 the LSB is used and if it is 1 the MSB is used.
----------------
IMO this adds a bit of clarity.


================
Comment at: libcxx/include/string:1492
+    void __set_short_size(size_type __s) _NOEXCEPT {
+        if (__s > 127)
+            __libcpp_unreachable();
----------------
I think I understand that you based the bound on the number representable by the `__size_` bitfield. We could also establish it based on the maximum capacity of the small string buffer by using `__s >= __min_cap` instead. (`>=` because of the null terminator).


================
Comment at: libcxx/include/string:1500-1501
+    size_type __get_short_size() const _NOEXCEPT {
+        if (__r_.first().__s.__is_long_)
+            __libcpp_unreachable();
+        return __r_.first().__s.__size_;
----------------
Same comment about using `_LIBCPP_ASSERT` -- unless we have a very convincing reason not to use it, let's use it.


================
Comment at: libcxx/include/string:3222
     size_type __m = __alloc_traits::max_size(__alloc());
-#ifdef _LIBCPP_BIG_ENDIAN
-    return (__m <= ~__long_mask ? __m : __m/2) - __alignment;
-#else
-    return __m - __alignment;
-#endif
+    return ((__m <= std::numeric_limits<size_type>::max() / 2) ? __m : __m / (2 / __endian_factor)) - __alignment;
 }
----------------
After discussing it, I think we agree this is a bit easier to digest.


================
Comment at: libcxx/include/string:3226
 template <class _CharT, class _Traits, class _Allocator>
 void
 basic_string<_CharT, _Traits, _Allocator>::reserve(size_type __requested_capacity)
----------------
I see the CI is failing on backdeployment macOS arm64. You just explained to me that this was caused by a bug where we used to return an incorrect `max_size()` in Big Endian -- the test would expect that we throw `length_error`, but we don't and instead we try allocating, which leads to `bad_alloc` instead. This patch fixes that, but since `max_size()` has been inlined into the dylib, the test will fail in backdeployment.

Since it seems that we are fixing a bug as part of this patch, let's add a regression test for it. We can then mark that test (and the failing `over_max_size.pass.cpp`) as `// XFAIL: use_system_cxx_lib && target=arm64{{.+}}-apple-macosx{{11.0|12.0}}`, with an appropriate comment.


================
Comment at: libcxx/utils/gdb/libcxx/printers.py:1
 #===----------------------------------------------------------------------===##
 #
----------------
@saugustine The pretty-printer tests have been disabled on recent Clang versions since D118067. It would be nice to fix them.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D123580/new/

https://reviews.llvm.org/D123580



More information about the libcxx-commits mailing list