Skip to content

Commit a349092

Browse files
committed
[libc++] Add __small_buffer
This is an implementation detail for `move_only_function` (and potentially other type-erasing classes). Reviewed By: #libc, ldionne Spies: Mordante, ldionne, EricWF, libcxx-commits Differential Revision: https://reviews.llvm.org/D140259
1 parent a6d0e87 commit a349092

File tree

4 files changed

+175
-0
lines changed

4 files changed

+175
-0
lines changed

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ set(files
854854
__utility/piecewise_construct.h
855855
__utility/priority_tag.h
856856
__utility/rel_ops.h
857+
__utility/small_buffer.h
857858
__utility/swap.h
858859
__utility/to_underlying.h
859860
__utility/unreachable.h
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef _LIBCPP___UTILITY_SMALL_BUFFER_H
10+
#define _LIBCPP___UTILITY_SMALL_BUFFER_H
11+
12+
#include <__config>
13+
#include <__memory/construct_at.h>
14+
#include <__type_traits/decay.h>
15+
#include <__type_traits/is_trivially_destructible.h>
16+
#include <__type_traits/is_trivially_move_constructible.h>
17+
#include <__utility/exception_guard.h>
18+
#include <__utility/forward.h>
19+
#include <cstddef>
20+
#include <new>
21+
22+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
23+
# pragma GCC system_header
24+
#endif
25+
26+
#if _LIBCPP_STD_VER >= 23
27+
28+
// __small_buffer is a helper class to perform the well known SBO (small buffer optimization). It is mainly useful to
29+
// allow type-erasing classes like move_only_function to store small objects in a local buffer without requiring an
30+
// allocation.
31+
//
32+
// This small buffer class only allows storing trivially relocatable objects inside the local storage to allow
33+
// __small_buffer to be trivially relocatable itself. Since the buffer doesn't know what's stored inside it, the user
34+
// has to manage the object's lifetime, in particular the destruction of the object.
35+
36+
_LIBCPP_BEGIN_NAMESPACE_STD
37+
38+
template <size_t _BufferSize, size_t _BufferAlignment>
39+
requires(_BufferSize > 0 && _BufferAlignment > 0)
40+
class __small_buffer {
41+
public:
42+
template <class _Tp, class _Decayed = decay_t<_Tp>>
43+
static constexpr bool __fits_in_buffer =
44+
is_trivially_move_constructible_v<_Decayed> && is_trivially_destructible_v<_Decayed> &&
45+
sizeof(_Decayed) <= _BufferSize && alignof(_Decayed) <= _BufferAlignment;
46+
47+
_LIBCPP_HIDE_FROM_ABI __small_buffer() = default;
48+
__small_buffer(const __small_buffer&) = delete;
49+
__small_buffer& operator=(const __small_buffer&) = delete;
50+
_LIBCPP_HIDE_FROM_ABI ~__small_buffer() = default;
51+
52+
// Relocates the buffer - __delete() should never be called on a moved-from __small_buffer
53+
_LIBCPP_HIDE_FROM_ABI __small_buffer(__small_buffer&&) = default;
54+
_LIBCPP_HIDE_FROM_ABI __small_buffer& operator=(__small_buffer&&) = default;
55+
56+
template <class _Stored>
57+
_LIBCPP_HIDE_FROM_ABI _Stored* __get() {
58+
if constexpr (__fits_in_buffer<_Stored>)
59+
return std::launder(reinterpret_cast<_Stored*>(__buffer_));
60+
else
61+
return *std::launder(reinterpret_cast<_Stored**>(__buffer_));
62+
}
63+
64+
template <class _Stored>
65+
_LIBCPP_AVAILABILITY_SIZED_NEW_DELETE _LIBCPP_HIDE_FROM_ABI _Stored* __alloc() {
66+
if constexpr (__fits_in_buffer<_Stored>) {
67+
return std::launder(reinterpret_cast<_Stored*>(__buffer_));
68+
} else {
69+
byte* __allocation = static_cast<byte*>(::operator new[](sizeof(_Stored), align_val_t{alignof(_Stored)}));
70+
std::construct_at(reinterpret_cast<byte**>(__buffer_), __allocation);
71+
return std::launder(reinterpret_cast<_Stored*>(__allocation));
72+
}
73+
}
74+
75+
template <class _Stored>
76+
_LIBCPP_AVAILABILITY_SIZED_NEW_DELETE _LIBCPP_HIDE_FROM_ABI void __dealloc() noexcept {
77+
if constexpr (!__fits_in_buffer<_Stored>)
78+
::operator delete[](*reinterpret_cast<void**>(__buffer_), sizeof(_Stored), align_val_t{alignof(_Stored)});
79+
}
80+
81+
template <class _Stored, class... _Args>
82+
_LIBCPP_AVAILABILITY_SIZED_NEW_DELETE _LIBCPP_HIDE_FROM_ABI void __construct(_Args&&... __args) {
83+
_Stored* __buffer = __alloc<_Stored>();
84+
auto __guard = std::__make_exception_guard([&] { __dealloc<_Stored>(); });
85+
std::construct_at(__buffer, std::forward<_Args>(__args)...);
86+
__guard.__complete();
87+
}
88+
89+
private:
90+
alignas(_BufferAlignment) byte __buffer_[_BufferSize];
91+
};
92+
93+
# undef _LIBCPP_SMALL_BUFFER_TRIVIAL_ABI
94+
95+
_LIBCPP_END_NAMESPACE_STD
96+
97+
#endif // _LIBCPP_STD_VER >= 23
98+
99+
#endif // _LIBCPP___UTILITY_SMALL_BUFFER_H

libcxx/include/module.modulemap.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2079,6 +2079,7 @@ module std_private_utility_pair_fwd [system] { header "__fwd/pair.
20792079
module std_private_utility_piecewise_construct [system] { header "__utility/piecewise_construct.h" }
20802080
module std_private_utility_priority_tag [system] { header "__utility/priority_tag.h" }
20812081
module std_private_utility_rel_ops [system] { header "__utility/rel_ops.h" }
2082+
module std_private_utility_small_buffer [system] { header "__utility/small_buffer.h" }
20822083
module std_private_utility_swap [system] {
20832084
header "__utility/swap.h"
20842085
export std_private_type_traits_is_swappable
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// XFAIL: availability-aligned_allocation-missing
10+
11+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
12+
13+
#include "test_macros.h"
14+
15+
TEST_DIAGNOSTIC_PUSH
16+
TEST_CLANG_DIAGNOSTIC_IGNORED("-Wprivate-header")
17+
#include <__utility/small_buffer.h>
18+
TEST_DIAGNOSTIC_POP
19+
20+
#include <cassert>
21+
#include <memory>
22+
#include <utility>
23+
24+
struct NotTriviallyRelocatable {
25+
char c_;
26+
27+
NotTriviallyRelocatable(char c) : c_(c) {}
28+
~NotTriviallyRelocatable() {}
29+
};
30+
31+
struct alignas(16) Overaligned {
32+
int i;
33+
};
34+
35+
int main(int, char**) {
36+
using BufferT = std::__small_buffer<8, 8>;
37+
static_assert(sizeof(BufferT) == 8);
38+
static_assert(alignof(BufferT) == 8);
39+
static_assert(BufferT::__fits_in_buffer<int>);
40+
static_assert(!BufferT::__fits_in_buffer<Overaligned>);
41+
static_assert(!BufferT::__fits_in_buffer<NotTriviallyRelocatable>);
42+
43+
BufferT buf;
44+
45+
{ // construct/destroy in the same place
46+
buf.__construct<int>(3);
47+
assert(*buf.__get<int>() == 3);
48+
std::destroy_at(buf.__get<int>());
49+
buf.__dealloc<int>();
50+
51+
buf.__construct<NotTriviallyRelocatable>(3);
52+
assert(buf.__get<NotTriviallyRelocatable>()->c_ == 3);
53+
std::destroy_at(buf.__get<NotTriviallyRelocatable>());
54+
buf.__dealloc<NotTriviallyRelocatable>();
55+
}
56+
57+
{ // Move the buffer around
58+
buf.__construct<int>(3);
59+
assert(*buf.__get<int>() == 3);
60+
auto buf2 = std::move(buf);
61+
assert(*buf2.__get<int>() == 3);
62+
std::destroy_at(buf2.__get<int>());
63+
buf2.__dealloc<int>();
64+
65+
buf.__construct<NotTriviallyRelocatable>(3);
66+
assert(buf.__get<NotTriviallyRelocatable>()->c_ == 3);
67+
auto buf3 = std::move(buf);
68+
assert(buf3.__get<NotTriviallyRelocatable>()->c_ == 3);
69+
std::destroy_at(buf3.__get<NotTriviallyRelocatable>());
70+
buf3.__dealloc<NotTriviallyRelocatable>();
71+
}
72+
73+
return 0;
74+
}

0 commit comments

Comments
 (0)