Skip to content

[libc++] Optimize vector growing of trivially relocatable types #76657

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

Merged
merged 1 commit into from
Feb 2, 2024

Conversation

philnik777
Copy link
Contributor

@philnik777 philnik777 commented Dec 31, 2023

This patch introduces a new trait to represent whether a type is trivially
relocatable, and uses that trait to optimize the growth of a std::vector
of trivially relocatable objects.

--------------------------------------------------
Benchmark                           old        new
--------------------------------------------------
bm_grow<int>                    1354 ns    1301 ns
bm_grow<std::string>            5584 ns    3370 ns
bm_grow<std::unique_ptr<int>>   3506 ns    1994 ns
bm_grow<std::deque<int>>       27114 ns   27209 ns

This also changes to order of moving and destroying the objects when
growing the vector. This should not affect our conformance.

@philnik777 philnik777 added libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. performance labels Dec 31, 2023
@philnik777 philnik777 requested a review from a team as a code owner December 31, 2023 13:28
@llvmbot
Copy link
Member

llvmbot commented Dec 31, 2023

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

Changes
--------------------------------------------------
Benchmark                           old        new
--------------------------------------------------
bm_grow&lt;int&gt;                    1354 ns    1301 ns
bm_grow&lt;std::string&gt;            5584 ns    3370 ns
bm_grow&lt;std::unique_ptr&lt;int&gt;&gt;   3506 ns    1994 ns
bm_grow&lt;std::deque&lt;int&gt;&gt;       27114 ns   27209 ns

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

8 Files Affected:

  • (modified) libcxx/benchmarks/vector_operations.bench.cpp (+16)
  • (modified) libcxx/docs/ReleaseNotes/18.rst (+2)
  • (modified) libcxx/include/CMakeLists.txt (+1)
  • (modified) libcxx/include/__fwd/string.h (+6)
  • (modified) libcxx/include/__memory/uninitialized_algorithms.h (+20)
  • (modified) libcxx/include/__memory/unique_ptr.h (+4)
  • (added) libcxx/include/__type_traits/is_trivially_relocatable.h (+32)
  • (modified) libcxx/include/vector (+28-11)
diff --git a/libcxx/benchmarks/vector_operations.bench.cpp b/libcxx/benchmarks/vector_operations.bench.cpp
index 38b14c56756fba..d7d8ac4358aef4 100644
--- a/libcxx/benchmarks/vector_operations.bench.cpp
+++ b/libcxx/benchmarks/vector_operations.bench.cpp
@@ -1,6 +1,7 @@
 #include <cstdint>
 #include <cstdlib>
 #include <cstring>
+#include <deque>
 #include <functional>
 #include <vector>
 
@@ -41,4 +42,19 @@ BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_string, std::vector<std::string>
 
 BENCHMARK_CAPTURE(BM_Pushback, vector_int, std::vector<int>{})->Arg(TestNumInputs);
 
+template <class T>
+void bm_grow(benchmark::State& state) {
+  for (auto _ : state) {
+    std::vector<T> vec;
+    benchmark::DoNotOptimize(vec);
+    for (size_t i = 0; i != 2048; ++i)
+      vec.emplace_back();
+    benchmark::DoNotOptimize(vec);
+  }
+}
+BENCHMARK(bm_grow<int>);
+BENCHMARK(bm_grow<std::string>);
+BENCHMARK(bm_grow<std::unique_ptr<int>>);
+BENCHMARK(bm_grow<std::deque<int>>);
+
 BENCHMARK_MAIN();
diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index fa60a581652d6a..412140bf2bdbce 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -69,6 +69,8 @@ Improvements and New Features
 - ``std::for_each`` has been optimized for segmented iterators like ``std::deque::iterator`` in C++23 and
   later, which can lead up to 40x performance improvements.
 
+- The performance of growing ``std::vector`` has been improved for trivially relocatable types.
+
 - The library now provides several hardening modes under which common cases of library undefined behavior will be turned
   into a reliable program termination. The ``fast`` hardening mode enables a set of security-critical checks with
   minimal runtime overhead; the ``extensive`` hardening mode additionally enables relatively cheap checks that catch
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 0fe3ab44d2466e..d0436aeda3d157 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -803,6 +803,7 @@ set(files
   __type_traits/is_trivially_lexicographically_comparable.h
   __type_traits/is_trivially_move_assignable.h
   __type_traits/is_trivially_move_constructible.h
+  __type_traits/is_trivially_relocatable.h
   __type_traits/is_unbounded_array.h
   __type_traits/is_union.h
   __type_traits/is_unsigned.h
diff --git a/libcxx/include/__fwd/string.h b/libcxx/include/__fwd/string.h
index 032132374de5ed..b811f4a5653306 100644
--- a/libcxx/include/__fwd/string.h
+++ b/libcxx/include/__fwd/string.h
@@ -12,6 +12,8 @@
 #include <__availability>
 #include <__config>
 #include <__fwd/memory_resource.h>
+#include <__type_traits/is_fundamental.h>
+#include <__type_traits/is_trivially_relocatable.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -45,6 +47,10 @@ class _LIBCPP_TEMPLATE_VIS allocator;
 template <class _CharT, class _Traits = char_traits<_CharT>, class _Allocator = allocator<_CharT> >
 class _LIBCPP_TEMPLATE_VIS basic_string;
 
+// TODO: This could be extended to also allow custom allocators and possibly custom chars and char traits
+template <class _CharT>
+struct __libcpp_is_trivially_relocatable<basic_string<_CharT>> : is_fundamental<_CharT> {};
+
 using string = basic_string<char>;
 
 #ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
diff --git a/libcxx/include/__memory/uninitialized_algorithms.h b/libcxx/include/__memory/uninitialized_algorithms.h
index 2a4ecf655be287..3a8364de01617f 100644
--- a/libcxx/include/__memory/uninitialized_algorithms.h
+++ b/libcxx/include/__memory/uninitialized_algorithms.h
@@ -29,6 +29,7 @@
 #include <__type_traits/is_trivially_copy_constructible.h>
 #include <__type_traits/is_trivially_move_assignable.h>
 #include <__type_traits/is_trivially_move_constructible.h>
+#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/is_unbounded_array.h>
 #include <__type_traits/negation.h>
 #include <__type_traits/remove_const.h>
@@ -646,6 +647,25 @@ __uninitialized_allocator_move_if_noexcept(_Alloc&, _Iter1 __first1, _Iter1 __la
 }
 #endif // _LIBCPP_COMPILER_GCC
 
+template <class _Alloc, class _Tp>
+struct __allocator_has_trivial_destroy : _Not<__has_destroy<_Alloc, _Tp*>> {};
+
+template <class _Tp, class _Up>
+struct __allocator_has_trivial_destroy<allocator<_Tp>, _Up> : true_type {};
+
+template <class _Alloc, class _Tp>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
+__uninitialized_allocator_relocate(_Alloc& __alloc, _Tp* __first, _Tp* __last, _Tp* __out) {
+  if (__libcpp_is_constant_evaluated() || !__libcpp_is_trivially_relocatable<_Tp>::value ||
+      !__allocator_has_trivial_move_construct<_Alloc, _Tp>::value ||
+      !__allocator_has_trivial_destroy<_Alloc, _Tp>::value) {
+    std::__uninitialized_allocator_move_if_noexcept(__alloc, __first, __last, __out);
+    std::__allocator_destroy(__alloc, __first, __last);
+  } else {
+    __builtin_memcpy(__out, __first, sizeof(_Tp) * (__last - __first));
+  }
+}
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP___MEMORY_UNINITIALIZED_ALGORITHMS_H
diff --git a/libcxx/include/__memory/unique_ptr.h b/libcxx/include/__memory/unique_ptr.h
index 7bf5e3c5e4e6b5..17f7adcdded29a 100644
--- a/libcxx/include/__memory/unique_ptr.h
+++ b/libcxx/include/__memory/unique_ptr.h
@@ -33,6 +33,7 @@
 #include <__type_traits/is_reference.h>
 #include <__type_traits/is_same.h>
 #include <__type_traits/is_swappable.h>
+#include <__type_traits/is_trivially_relocatable.h>
 #include <__type_traits/is_void.h>
 #include <__type_traits/remove_extent.h>
 #include <__type_traits/type_identity.h>
@@ -273,6 +274,9 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& __u) _NOEXCEPT { __ptr_.swap(__u.__ptr_); }
 };
 
+template <class _Tp>
+struct __libcpp_is_trivially_relocatable<unique_ptr<_Tp>> : true_type {};
+
 template <class _Tp, class _Dp>
 class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> {
 public:
diff --git a/libcxx/include/__type_traits/is_trivially_relocatable.h b/libcxx/include/__type_traits/is_trivially_relocatable.h
new file mode 100644
index 00000000000000..4b95d972ebcf9d
--- /dev/null
+++ b/libcxx/include/__type_traits/is_trivially_relocatable.h
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H
+#define _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H
+
+#include <__config>
+#include <__type_traits/integral_constant.h>
+#include <__type_traits/is_trivial.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if __has_builtin(__is_trivially_relocatable)
+template <class _Tp>
+struct __libcpp_is_trivially_relocatable : integral_constant<bool, __is_trivially_relocatable(_Tp)> {};
+#else
+template <class _Tp>
+struct __libcpp_is_trivially_relocatable : is_trivial<_Tp> {};
+#endif
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___TYPE_TRAITS_IS_TRIVIALLY_RELOCATABLE_H
diff --git a/libcxx/include/vector b/libcxx/include/vector
index 0098273a195ff8..1e368fdcb8d3dd 100644
--- a/libcxx/include/vector
+++ b/libcxx/include/vector
@@ -982,14 +982,18 @@ template <ranges::input_range _Range,
 vector(from_range_t, _Range&&, _Alloc = _Alloc()) -> vector<ranges::range_value_t<_Range>, _Alloc>;
 #endif
 
+// __swap_out_circular_buffer relocates the objects in [__begin_, __end_) into the front of __v and swaps the buffers of
+// *this and __v. It is assumed that __v provides space for exactly (__end_ - __begin_) objects in the front. This
+// function has a strong exception guarantee.
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 void
 vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, allocator_type&>& __v) {
   __annotate_delete();
-  using _RevIter = std::reverse_iterator<pointer>;
-  __v.__begin_   = std::__uninitialized_allocator_move_if_noexcept(
-                     __alloc(), _RevIter(__end_), _RevIter(__begin_), _RevIter(__v.__begin_))
-                     .base();
+  auto __new_begin = __v.__begin_ - (__end_ - __begin_);
+  std::__uninitialized_allocator_relocate(
+      __alloc(), std::__to_address(__begin_), std::__to_address(__end_), std::__to_address(__new_begin));
+  __v.__begin_ = __new_begin;
+  __end_       = __begin_; // All the objects have been destroyed by relocating them.
   std::swap(this->__begin_, __v.__begin_);
   std::swap(this->__end_, __v.__end_);
   std::swap(this->__end_cap(), __v.__end_cap());
@@ -997,22 +1001,35 @@ vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, a
   __annotate_new(size());
 }
 
+// __swap_out_circular_buffer relocates the objects in [__begin_, __p) into the front of __v, the objects in
+// [__p, __end_) into the back of __v and swaps the buffers of *this and __v. It is assumed that __v provides space for
+// exactly (__p - __begin_) objects in the front and space for at least (__end_ - __p) objects in the back. This
+// function has a strong exception guarantee if __begin_ == __p || __end_ == __p.
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::pointer
 vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, allocator_type&>& __v, pointer __p) {
   __annotate_delete();
-  pointer __r    = __v.__begin_;
-  using _RevIter = std::reverse_iterator<pointer>;
-  __v.__begin_   = std::__uninitialized_allocator_move_if_noexcept(
-                     __alloc(), _RevIter(__p), _RevIter(__begin_), _RevIter(__v.__begin_))
-                     .base();
-  __v.__end_ = std::__uninitialized_allocator_move_if_noexcept(__alloc(), __p, __end_, __v.__end_);
+  pointer __ret = __v.__begin_;
+
+  // Relocate [__p, __end_) first to avoid having a hole in [__begin_, __end_)
+  // in case something in [__begin_, __p) throws.
+  std::__uninitialized_allocator_relocate(
+      __alloc(), std::__to_address(__p), std::__to_address(__end_), std::__to_address(__v.__end_));
+  __v.__end_ += (__end_ - __p);
+  __end_           = __p; // The objects in [__p, __end_) have been destroyed by relocating them.
+  auto __new_begin = __v.__begin_ - (__p - __begin_);
+
+  std::__uninitialized_allocator_relocate(
+      __alloc(), std::__to_address(__begin_), std::__to_address(__p), std::__to_address(__new_begin));
+  __v.__begin_ = __new_begin;
+  __end_       = __begin_; // All the objects have been destroyed by relocating them.
+
   std::swap(this->__begin_, __v.__begin_);
   std::swap(this->__end_, __v.__end_);
   std::swap(this->__end_cap(), __v.__end_cap());
   __v.__first_ = __v.__begin_;
   __annotate_new(size());
-  return __r;
+  return __ret;
 }
 
 template <class _Tp, class _Allocator>

Copy link
Member

@mordante mordante left a comment

Choose a reason for hiding this comment

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

This looks interesting, some comments.

@@ -982,37 +982,54 @@ template <ranges::input_range _Range,
vector(from_range_t, _Range&&, _Alloc = _Alloc()) -> vector<ranges::range_value_t<_Range>, _Alloc>;
#endif

// __swap_out_circular_buffer relocates the objects in [__begin_, __end_) into the front of __v and swaps the buffers of
// *this and __v. It is assumed that __v provides space for exactly (__end_ - __begin_) objects in the front. This
Copy link
Member

Choose a reason for hiding this comment

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

In a followup NFC, we absolutely need to change the name of __v here to something like __sb, because __v implies that the variable is a vector, when in fact it isn't. Or __temporary_buffer, or anything else but not __v.

@ldionne ldionne self-assigned this Jan 11, 2024
@philnik777 philnik777 force-pushed the uninitialized_allocator_relocate branch from 46b8f19 to 0869709 Compare January 12, 2024 12:58
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this file be renamed to is_trivially_equality_comparable.compile.pass.cpp?

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

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

This LGTM once all comments have been addressed. This is pretty exciting!

@philnik777 philnik777 force-pushed the uninitialized_allocator_relocate branch 3 times, most recently from 8f51c55 to f5e9693 Compare January 22, 2024 14:01
@ldionne ldionne added this to the LLVM 19.0.X Release milestone Jan 22, 2024
@philnik777 philnik777 force-pushed the uninitialized_allocator_relocate branch 3 times, most recently from 49245ae to f99f079 Compare January 31, 2024 16:55
Copy link

github-actions bot commented Jan 31, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@philnik777 philnik777 force-pushed the uninitialized_allocator_relocate branch from f99f079 to fec48e1 Compare February 1, 2024 11:23
@ldionne
Copy link
Member

ldionne commented Feb 1, 2024

Let's fix the last nit and land this. Thanks for this patch, it's a really important optimization.

@philnik777 philnik777 force-pushed the uninitialized_allocator_relocate branch from fec48e1 to 60b65f5 Compare February 2, 2024 16:13
@philnik777 philnik777 merged commit 67eee4a into llvm:main Feb 2, 2024
@philnik777 philnik777 deleted the uninitialized_allocator_relocate branch February 2, 2024 16:13
@kstoimenov
Copy link
Contributor

kstoimenov commented Feb 2, 2024

This was reverted in 2352fdd due to sanitizer build bot breaking.

@philnik777
Copy link
Contributor Author

@kstoimenov Could you maybe give me some more information? "My bot broke" is really not helpful, and I'm not at all familiar with any of the non-libc++ code that is shown in the traces.

@kstoimenov
Copy link
Contributor

kstoimenov commented Feb 2, 2024

This is the log: https://lab.llvm.org/buildbot/#/builders/5/builds/40641/steps/9/logs/stdio

I believe your change might have introduced heap-use-after-free.

==132952==ERROR: AddressSanitizer: heap-use-after-free on address 0x50400002a650 at pc 0x55595f908ebf bp 0x7ffdc69d5cd0 sp 0x7ffdc69d5cc8
READ of size 8 at 0x50400002a650 thread T0
    #0 0x55595f908ebe in asInt /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:41:5
    #1 0x55595f908ebe in operator long /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:45:48
    #2 0x55595f908ebe in getPointer /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:94:58
    #3 0x55595f908ebe in getPrevPtr /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/ValueHandle.h:123:58
    #4 0x55595f908ebe in llvm::ValueHandleBase::RemoveFromUseList() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/IR/Value.cpp:1185:5
    #5 0x55595d60c3b1 in ~ValueHandleBase /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/ValueHandle.h:66:7
    #6 0x55595d60c3b1 in ~__optional_destruct_base /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/optional:299:15
    #7 0x55595d60c3b1 in ~pair /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__utility/pair.h:80:29
    #8 0x55595d60c3b1 in destroy /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocator.h:176:87
    #9 0x55595d60c3b1 in destroy<std::__1::pair<std::__1::optional<llvm::WeakTrackingVH>, llvm::CallGraphNode *>, void> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocator_traits.h:335:9
    #10 0x55595d60c3b1 in std::__1::vector<std::__1::pair<std::__1::optional<llvm::WeakTrackingVH>, llvm::CallGraphNode*>, std::__1::allocator<std::__1::pair<std::__1::optional<llvm::WeakTrackingVH>, llvm::CallGraphNode*>>>::__base_destruct_at_end[abi:nn190000](std::__1::pair<std::__1::optional<llvm::WeakTrackingVH>, llvm::CallGraphNode*>*) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:926:7
    #11 0x55595d60c7cd in __clear /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:920:5
    #12 0x55595d60c7cd in std::__1::vector<std::__1::pair<std::__1::optional<llvm::WeakTrackingVH>, llvm::CallGraphNode*>, std::__1::allocator<std::__1::pair<std::__1::optional<llvm::WeakTrackingVH>, llvm::CallGraphNode*>>>::__destroy_vector::operator()[abi:nn190000]() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:490:16
    #13 0x55595d6021e5 in ~vector /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:501:67
    #14 0x55595d6021e5 in ~CallGraphNode /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/Analysis/CallGraph.h:191:3
    #15 0x55595d6021e5 in operator() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/unique_ptr.h:68:5
    #16 0x55595d6021e5 in reset /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/unique_ptr.h:279:7
    #17 0x55595d6021e5 in std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>::~unique_ptr[abi:nn190000]() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/unique_ptr.h:249:71
    #18 0x55595d60ca13 in ~pair /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__utility/pair.h:80:29
    #19 0x55595d60ca13 in __destroy_at<std::__1::pair<const llvm::Function *const, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode> > >, 0> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/construct_at.h:67:11
    #20 0x55595d60ca13 in destroy<std::__1::pair<const llvm::Function *const, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode> > >, void, void> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocator_traits.h:340:5
    #21 0x55595d60ca13 in std::__1::__tree<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::__map_value_compare<llvm::Function const*, std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::less<llvm::Function const*>, true>, std::__1::allocator<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>>>::destroy(std::__1::__tree_node<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, void*>*) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__tree:1548:5
    #22 0x55595d60ca00 in std::__1::__tree<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::__map_value_compare<llvm::Function const*, std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::less<llvm::Function const*>, true>, std::__1::allocator<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>>>::destroy(std::__1::__tree_node<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, void*>*) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__tree:1546:5
    #23 0x55595d60ca00 in std::__1::__tree<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::__map_value_compare<llvm::Function const*, std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::less<llvm::Function const*>, true>, std::__1::allocator<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>>>::destroy(std::__1::__tree_node<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, void*>*) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__tree:1546:5
    #24 0x55595d60ca00 in std::__1::__tree<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::__map_value_compare<llvm::Function const*, std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::less<llvm::Function const*>, true>, std::__1::allocator<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>>>::destroy(std::__1::__tree_node<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, void*>*) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__tree:1546:5
    #25 0x55595d60ca00 in std::__1::__tree<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::__map_value_compare<llvm::Function const*, std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, std::__1::less<llvm::Function const*>, true>, std::__1::allocator<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>>>::destroy(std::__1::__tree_node<std::__1::__value_type<llvm::Function const*, std::__1::unique_ptr<llvm::CallGraphNode, std::__1::default_delete<llvm::CallGraphNode>>>, void*>*) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__tree:1546:5
    #26 0x555959d7cbce in (anonymous namespace)::AMDGPULowerModuleLDS::runOnModule(llvm::Module&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Target/AMDGPU/AMDGPULowerModuleLDSPass.cpp:1322:3
    #27 0x555959d82658 in (anonymous namespace)::AMDGPULowerModuleLDSLegacy::runOnModule(llvm::Module&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Target/AMDGPU/AMDGPULowerModuleLDSPass.cpp:1605:38
    #28 0x55595f7a622d in runOnModule /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/IR/LegacyPassManager.cpp:1560:27
    #29 0x55595f7a622d in llvm::legacy::PassManagerImpl::run(llvm::Module&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/IR/LegacyPassManager.cpp:542:44
    #30 0x555958a40906 in compileModule(char**, llvm::LLVMContext&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/tools/llc/llc.cpp:743:8
    #31 0x555958a39bff in main /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/tools/llc/llc.cpp:412:22
    #32 0x7fe0a4623a8f  (/lib/x86_64-linux-gnu/libc.so.6+0x23a8f) (BuildId: d320ce4e63925d698610ed423fc4b1f0e8ed51f1)
    #33 0x7fe0a4623b48 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x23b48) (BuildId: d320ce4e63925d698610ed423fc4b1f0e8ed51f1)
    #34 0x555958953c64 in _start (/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/llc+0xabc7c64)

@philnik777
Copy link
Contributor Author

@kstoimenov I see that there is a heap-use-after-free reported, but I don't see how that is related to this patch. AFAICT none of the types that vector got instantiated with in this backtrace is trivially relocatable, so this patch shouldn't have changed anything there. Would it be possible to get a smaller reproducer than all of Clang?

@kstoimenov
Copy link
Contributor

@philnik777 are you running your tests under ASAN? If not can you try running them with -fsanitize=address? I don't think I can help you with a reproducer because I am not working on libc++. Also FYI this is the link to the sanitizer dashboards: https://google.github.io/sanitizers/show_bots.html.

I also added the full report which tells you where the allocation and the deallocation happened.

From https://lab.llvm.org/buildbot/#/builders/5/builds/40641/steps/9/logs/stdio

==2097169==ERROR: AddressSanitizer: heap-use-after-free on address 0x50300000a2a0 at pc 0x55d6ac624faf bp 0x7ffd01fc2830 sp 0x7ffd01fc2828
READ of size 8 at 0x50300000a2a0 thread T0
    #0 0x55d6ac624fae in asInt /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:41:5
    #1 0x55d6ac624fae in operator long /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:45:48
    #2 0x55d6ac624fae in getPointer /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/ADT/PointerIntPair.h:94:58
    #3 0x55d6ac624fae in getPrevPtr /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/ValueHandle.h:123:58
    #4 0x55d6ac624fae in llvm::ValueHandleBase::RemoveFromUseList() /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/IR/Value.cpp:1185:5
    #5 0x55d6ae2c476d in ~ValueHandleBase /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/ValueHandle.h:66:7
    #6 0x55d6ae2c476d in destroy /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocator.h:176:87
    #7 0x55d6ae2c476d in destroy<llvm::WeakTrackingVH, void> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocator_traits.h:335:9
    #8 0x55d6ae2c476d in __base_destruct_at_end /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:926:7
    #9 0x55d6ae2c476d in __clear /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:920:5
    #10 0x55d6ae2c476d in std::__1::vector<llvm::WeakTrackingVH, std::__1::allocator<llvm::WeakTrackingVH>>::__destroy_vector::operator()[abi:nn190000]() /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:490:16
    #11 0x55d6b183e18e in ~vector /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:501:67
    #12 0x55d6b183e18e in (anonymous namespace)::MergeFunctions::runOnModule(llvm::Module&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Transforms/IPO/MergeFunctions.cpp:454:3
    #13 0x55d6b183667d in llvm::MergeFunctionsPass::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Transforms/IPO/MergeFunctions.cpp:300:11
    #14 0x55d6b0450d71 in llvm::detail::PassModel<llvm::Module, llvm::MergeFunctionsPass, llvm::PreservedAnalyses, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:89:17
    #15 0x55d6aca6b1f5 in llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/PassManager.h:543:40
    #16 0x55d6b02f48c8 in llvm::runPassPipeline(llvm::StringRef, llvm::Module&, llvm::TargetMachine*, llvm::TargetLibraryInfoImpl*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::StringRef, llvm::ArrayRef<llvm::PassPlugin>, llvm::ArrayRef<std::__1::function<void (llvm::PassBuilder&)>>, llvm::opt_tool::OutputKind, llvm::opt_tool::VerifierKind, bool, bool, bool, bool, bool, bool, bool) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/tools/opt/NewPMDriver.cpp:532:7
    #17 0x55d6ac3a2f76 in optMain /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/tools/opt/optdriver.cpp:741:12
    #18 0x7f21e6a23a8f  (/lib/x86_64-linux-gnu/libc.so.6+0x23a8f) (BuildId: d320ce4e63925d698610ed423fc4b1f0e8ed51f1)
    #19 0x7f21e6a23b48 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x23b48) (BuildId: d320ce4e63925d698610ed423fc4b1f0e8ed51f1)
    #20 0x55d6ac2b6724 in _start (/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/opt+0xabd4724)
0x50300000a2a0 is located 0 bytes inside of 24-byte region [0x50300000a2a0,0x50300000a2b8)
freed by thread T0 here:
    #0 0x55d6ac38fe9d in operator delete(void*) /b/sanitizer-x86_64-linux-fast/build/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:152:3
    #1 0x55d6ae004b2c in __libcpp_operator_delete<void *> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/new:280:3
    #2 0x55d6ae004b2c in __do_deallocate_handle_size<> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/new:302:10
    #3 0x55d6ae004b2c in __libcpp_deallocate /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/new:317:12
    #4 0x55d6ae004b2c in deallocate /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocator.h:139:7
    #5 0x55d6ae004b2c in deallocate /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocator_traits.h:313:9
    #6 0x55d6ae004b2c in ~__split_buffer /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__split_buffer:355:5
    #7 0x55d6ae004b2c in llvm::WeakTrackingVH* std::__1::vector<llvm::WeakTrackingVH, std::__1::allocator<llvm::WeakTrackingVH>>::__push_back_slow_path<llvm::WeakTrackingVH>(llvm::WeakTrackingVH&&) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:1474:1
    #8 0x55d6b18387be in push_back /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:1496:13
    #9 0x55d6b18387be in (anonymous namespace)::MergeFunctions::runOnModule(llvm::Module&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Transforms/IPO/MergeFunctions.cpp:431:16
    #10 0x55d6b183667d in llvm::MergeFunctionsPass::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Transforms/IPO/MergeFunctions.cpp:300:11
    #11 0x55d6b0450d71 in llvm::detail::PassModel<llvm::Module, llvm::MergeFunctionsPass, llvm::PreservedAnalyses, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:89:17
    #12 0x55d6aca6b1f5 in llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/PassManager.h:543:40
    #13 0x55d6b02f48c8 in llvm::runPassPipeline(llvm::StringRef, llvm::Module&, llvm::TargetMachine*, llvm::TargetLibraryInfoImpl*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::StringRef, llvm::ArrayRef<llvm::PassPlugin>, llvm::ArrayRef<std::__1::function<void (llvm::PassBuilder&)>>, llvm::opt_tool::OutputKind, llvm::opt_tool::VerifierKind, bool, bool, bool, bool, bool, bool, bool) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/tools/opt/NewPMDriver.cpp:532:7
    #14 0x55d6ac3a2f76 in optMain /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/tools/opt/optdriver.cpp:741:12
    #15 0x7f21e6a23a8f  (/lib/x86_64-linux-gnu/libc.so.6+0x23a8f) (BuildId: d320ce4e63925d698610ed423fc4b1f0e8ed51f1)
previously allocated by thread T0 here:
    #0 0x55d6ac38f63d in operator new(unsigned long) /b/sanitizer-x86_64-linux-fast/build/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:95:3
    #1 0x55d6ae004de8 in __libcpp_operator_new<unsigned long> /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/new:271:10
    #2 0x55d6ae004de8 in __libcpp_allocate /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/new:295:10
    #3 0x55d6ae004de8 in allocate /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocator.h:125:32
    #4 0x55d6ae004de8 in __allocate_at_least<std::__1::allocator<llvm::WeakTrackingVH> > /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__memory/allocate_at_least.h:41:19
    #5 0x55d6ae004de8 in std::__1::__split_buffer<llvm::WeakTrackingVH, std::__1::allocator<llvm::WeakTrackingVH>&>::__split_buffer(unsigned long, unsigned long, std::__1::allocator<llvm::WeakTrackingVH>&) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/__split_buffer:343:25
    #6 0x55d6ae00496e in llvm::WeakTrackingVH* std::__1::vector<llvm::WeakTrackingVH, std::__1::allocator<llvm::WeakTrackingVH>>::__push_back_slow_path<llvm::WeakTrackingVH>(llvm::WeakTrackingVH&&) /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:1468:47
    #7 0x55d6b18384bf in push_back /b/sanitizer-x86_64-linux-fast/build/libcxx_build_asan_ubsan/include/c++/v1/vector:1496:13
    #8 0x55d6b18384bf in (anonymous namespace)::MergeFunctions::runOnModule(llvm::Module&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Transforms/IPO/MergeFunctions.cpp:431:16
    #9 0x55d6b183667d in llvm::MergeFunctionsPass::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/lib/Transforms/IPO/MergeFunctions.cpp:300:11
    #10 0x55d6b0450d71 in llvm::detail::PassModel<llvm::Module, llvm::MergeFunctionsPass, llvm::PreservedAnalyses, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:89:17
    #11 0x55d6aca6b1f5 in llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/include/llvm/IR/PassManager.h:543:40
    #12 0x55d6b02f48c8 in llvm::runPassPipeline(llvm::StringRef, llvm::Module&, llvm::TargetMachine*, llvm::TargetLibraryInfoImpl*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::StringRef, llvm::ArrayRef<llvm::PassPlugin>, llvm::ArrayRef<std::__1::function<void (llvm::PassBuilder&)>>, llvm::opt_tool::OutputKind, llvm::opt_tool::VerifierKind, bool, bool, bool, bool, bool, bool, bool) /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/tools/opt/NewPMDriver.cpp:532:7
    #13 0x55d6ac3a2f76 in optMain /b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/tools/opt/optdriver.cpp:741:12
    #14 0x7f21e6a23a8f  (/lib/x86_64-linux-gnu/libc.so.6+0x23a8f) (BuildId: d320ce4e63925d698610ed423fc4b1f0e8ed51f1)

@philnik777
Copy link
Contributor Author

@kstoimenov Yes, we always run the full test suite with all the different sanitizers enabled. I've also tried to run it with optimizations enabled, and everything works fine. From my point of view this could be just as much a bug in LLVM as in libc++.

philnik777 added a commit to philnik777/llvm-project that referenced this pull request Feb 3, 2024
philnik777 added a commit to philnik777/llvm-project that referenced this pull request Feb 3, 2024
philnik777 added a commit to philnik777/llvm-project that referenced this pull request Feb 3, 2024
philnik777 added a commit to philnik777/llvm-project that referenced this pull request Feb 3, 2024
philnik777 added a commit that referenced this pull request Feb 3, 2024
…pes" (#80558)

This reapplies #76657. Non-trivial elements didn't get destroyed
previously. This fixes the bug and adds tests for all the vector
insertion functions.
agozillon pushed a commit to agozillon/llvm-project that referenced this pull request Feb 5, 2024
…#76657)

This patch introduces a new trait to represent whether a type is
trivially
relocatable, and uses that trait to optimize the growth of a std::vector
of trivially relocatable objects.

```
--------------------------------------------------
Benchmark                           old        new
--------------------------------------------------
bm_grow<int>                    1354 ns    1301 ns
bm_grow<std::string>            5584 ns    3370 ns
bm_grow<std::unique_ptr<int>>   3506 ns    1994 ns
bm_grow<std::deque<int>>       27114 ns   27209 ns
```

This also changes to order of moving and destroying the objects when
growing the vector. This should not affect our conformance.
agozillon pushed a commit to agozillon/llvm-project that referenced this pull request Feb 5, 2024
agozillon pushed a commit to agozillon/llvm-project that referenced this pull request Feb 5, 2024
…pes" (llvm#80558)

This reapplies llvm#76657. Non-trivial elements didn't get destroyed
previously. This fixes the bug and adds tests for all the vector
insertion functions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants