1 //===- FunctionExtras.h - Function type erasure utilities -------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 /// This file provides a collection of function (or more generally, callable)
10 /// type erasure utilities supplementing those provided by the standard library
13 /// It provides `unique_function`, which works like `std::function` but supports
14 /// move-only callable objects.
17 /// - Add a `function` that provides const, volatile, and ref-qualified support,
18 /// which doesn't work with `std::function`.
19 /// - Provide support for specifying multiple signatures to type erase callable
20 /// objects with an overload set, such as those produced by generic lambdas.
21 /// - Expand to include a copyable utility that directly replaces std::function
22 /// but brings the above improvements.
24 /// Note that LLVM's utilities are greatly simplified by not supporting
27 /// If the standard library ever begins to provide comparable facilities we can
28 /// consider switching to those.
30 //===----------------------------------------------------------------------===//
32 #ifndef LLVM_ADT_FUNCTION_EXTRAS_H
33 #define LLVM_ADT_FUNCTION_EXTRAS_H
35 #include "llvm/ADT/PointerIntPair.h"
36 #include "llvm/ADT/PointerUnion.h"
37 #include "llvm/Support/type_traits.h"
42 template <typename FunctionT
> class unique_function
;
44 template <typename ReturnT
, typename
... ParamTs
>
45 class unique_function
<ReturnT(ParamTs
...)> {
46 static constexpr size_t InlineStorageSize
= sizeof(void *) * 3;
48 // MSVC has a bug and ICEs if we give it a particular dependent value
49 // expression as part of the `std::conditional` below. To work around this,
50 // we build that into a template struct's constexpr bool.
51 template <typename T
> struct IsSizeLessThanThresholdT
{
52 static constexpr bool value
= sizeof(T
) <= (2 * sizeof(void *));
55 // Provide a type function to map parameters that won't observe extra copies
56 // or moves and which are small enough to likely pass in register to values
57 // and all other types to l-value reference types. We use this to compute the
58 // types used in our erased call utility to minimize copies and moves unless
59 // doing so would force things unnecessarily into memory.
61 // The heuristic used is related to common ABI register passing conventions.
62 // It doesn't have to be exact though, and in one way it is more strict
63 // because we want to still be able to observe either moves *or* copies.
65 using AdjustedParamT
= typename
std::conditional
<
66 !std::is_reference
<T
>::value
&&
67 llvm::is_trivially_copy_constructible
<T
>::value
&&
68 llvm::is_trivially_move_constructible
<T
>::value
&&
69 IsSizeLessThanThresholdT
<T
>::value
,
72 // The type of the erased function pointer we use as a callback to dispatch to
73 // the stored callable when it is trivial to move and destroy.
74 using CallPtrT
= ReturnT (*)(void *CallableAddr
,
75 AdjustedParamT
<ParamTs
>... Params
);
76 using MovePtrT
= void (*)(void *LHSCallableAddr
, void *RHSCallableAddr
);
77 using DestroyPtrT
= void (*)(void *CallableAddr
);
79 /// A struct to hold a single trivial callback with sufficient alignment for
81 struct alignas(8) TrivialCallback
{
85 /// A struct we use to aggregate three callbacks when we need full set of
87 struct alignas(8) NonTrivialCallbacks
{
90 DestroyPtrT DestroyPtr
;
93 // Create a pointer union between either a pointer to a static trivial call
94 // pointer in a struct or a pointer to a static struct of the call, move, and
96 using CallbackPointerUnionT
=
97 PointerUnion
<TrivialCallback
*, NonTrivialCallbacks
*>;
99 // The main storage buffer. This will either have a pointer to out-of-line
100 // storage or an inline buffer storing the callable.
101 union StorageUnionT
{
102 // For out-of-line storage we keep a pointer to the underlying storage and
103 // the size. This is enough to deallocate the memory.
104 struct OutOfLineStorageT
{
110 sizeof(OutOfLineStorageT
) <= InlineStorageSize
,
111 "Should always use all of the out-of-line storage for inline storage!");
113 // For in-line storage, we just provide an aligned character buffer. We
114 // provide three pointers worth of storage here.
115 typename
std::aligned_storage
<InlineStorageSize
, alignof(void *)>::type
119 // A compressed pointer to either our dispatching callback or our table of
120 // dispatching callbacks and the flag for whether the callable itself is
121 // stored inline or not.
122 PointerIntPair
<CallbackPointerUnionT
, 1, bool> CallbackAndInlineFlag
;
124 bool isInlineStorage() const { return CallbackAndInlineFlag
.getInt(); }
126 bool isTrivialCallback() const {
127 return CallbackAndInlineFlag
.getPointer().template is
<TrivialCallback
*>();
130 CallPtrT
getTrivialCallback() const {
131 return CallbackAndInlineFlag
.getPointer().template get
<TrivialCallback
*>()->CallPtr
;
134 NonTrivialCallbacks
*getNonTrivialCallbacks() const {
135 return CallbackAndInlineFlag
.getPointer()
136 .template get
<NonTrivialCallbacks
*>();
139 void *getInlineStorage() { return &StorageUnion
.InlineStorage
; }
141 void *getOutOfLineStorage() {
142 return StorageUnion
.OutOfLineStorage
.StoragePtr
;
144 size_t getOutOfLineStorageSize() const {
145 return StorageUnion
.OutOfLineStorage
.Size
;
147 size_t getOutOfLineStorageAlignment() const {
148 return StorageUnion
.OutOfLineStorage
.Alignment
;
151 void setOutOfLineStorage(void *Ptr
, size_t Size
, size_t Alignment
) {
152 StorageUnion
.OutOfLineStorage
= {Ptr
, Size
, Alignment
};
155 template <typename CallableT
>
156 static ReturnT
CallImpl(void *CallableAddr
, AdjustedParamT
<ParamTs
>... Params
) {
157 return (*reinterpret_cast<CallableT
*>(CallableAddr
))(
158 std::forward
<ParamTs
>(Params
)...);
161 template <typename CallableT
>
162 static void MoveImpl(void *LHSCallableAddr
, void *RHSCallableAddr
) noexcept
{
163 new (LHSCallableAddr
)
164 CallableT(std::move(*reinterpret_cast<CallableT
*>(RHSCallableAddr
)));
167 template <typename CallableT
>
168 static void DestroyImpl(void *CallableAddr
) noexcept
{
169 reinterpret_cast<CallableT
*>(CallableAddr
)->~CallableT();
173 unique_function() = default;
174 unique_function(std::nullptr_t
/*null_callable*/) {}
177 if (!CallbackAndInlineFlag
.getPointer())
180 // Cache this value so we don't re-check it after type-erased operations.
181 bool IsInlineStorage
= isInlineStorage();
183 if (!isTrivialCallback())
184 getNonTrivialCallbacks()->DestroyPtr(
185 IsInlineStorage
? getInlineStorage() : getOutOfLineStorage());
187 if (!IsInlineStorage
)
188 deallocate_buffer(getOutOfLineStorage(), getOutOfLineStorageSize(),
189 getOutOfLineStorageAlignment());
192 unique_function(unique_function
&&RHS
) noexcept
{
193 // Copy the callback and inline flag.
194 CallbackAndInlineFlag
= RHS
.CallbackAndInlineFlag
;
196 // If the RHS is empty, just copying the above is sufficient.
200 if (!isInlineStorage()) {
201 // The out-of-line case is easiest to move.
202 StorageUnion
.OutOfLineStorage
= RHS
.StorageUnion
.OutOfLineStorage
;
203 } else if (isTrivialCallback()) {
204 // Move is trivial, just memcpy the bytes across.
205 memcpy(getInlineStorage(), RHS
.getInlineStorage(), InlineStorageSize
);
207 // Non-trivial move, so dispatch to a type-erased implementation.
208 getNonTrivialCallbacks()->MovePtr(getInlineStorage(),
209 RHS
.getInlineStorage());
212 // Clear the old callback and inline flag to get back to as-if-null.
213 RHS
.CallbackAndInlineFlag
= {};
216 // In debug builds, we also scribble across the rest of the storage.
217 memset(RHS
.getInlineStorage(), 0xAD, InlineStorageSize
);
221 unique_function
&operator=(unique_function
&&RHS
) noexcept
{
225 // Because we don't try to provide any exception safety guarantees we can
226 // implement move assignment very simply by first destroying the current
227 // object and then move-constructing over top of it.
228 this->~unique_function();
229 new (this) unique_function(std::move(RHS
));
233 template <typename CallableT
> unique_function(CallableT Callable
) {
234 bool IsInlineStorage
= true;
235 void *CallableAddr
= getInlineStorage();
236 if (sizeof(CallableT
) > InlineStorageSize
||
237 alignof(CallableT
) > alignof(decltype(StorageUnion
.InlineStorage
))) {
238 IsInlineStorage
= false;
239 // Allocate out-of-line storage. FIXME: Use an explicit alignment
240 // parameter in C++17 mode.
241 auto Size
= sizeof(CallableT
);
242 auto Alignment
= alignof(CallableT
);
243 CallableAddr
= allocate_buffer(Size
, Alignment
);
244 setOutOfLineStorage(CallableAddr
, Size
, Alignment
);
247 // Now move into the storage.
248 new (CallableAddr
) CallableT(std::move(Callable
));
250 // See if we can create a trivial callback. We need the callable to be
251 // trivially moved and trivially destroyed so that we don't have to store
252 // type erased callbacks for those operations.
254 // FIXME: We should use constexpr if here and below to avoid instantiating
255 // the non-trivial static objects when unnecessary. While the linker should
256 // remove them, it is still wasteful.
257 if (llvm::is_trivially_move_constructible
<CallableT
>::value
&&
258 std::is_trivially_destructible
<CallableT
>::value
) {
259 // We need to create a nicely aligned object. We use a static variable
260 // for this because it is a trivial struct.
261 static TrivialCallback Callback
= { &CallImpl
<CallableT
> };
263 CallbackAndInlineFlag
= {&Callback
, IsInlineStorage
};
267 // Otherwise, we need to point at an object that contains all the different
268 // type erased behaviors needed. Create a static instance of the struct type
269 // here and then use a pointer to that.
270 static NonTrivialCallbacks Callbacks
= {
271 &CallImpl
<CallableT
>, &MoveImpl
<CallableT
>, &DestroyImpl
<CallableT
>};
273 CallbackAndInlineFlag
= {&Callbacks
, IsInlineStorage
};
276 ReturnT
operator()(ParamTs
... Params
) {
278 isInlineStorage() ? getInlineStorage() : getOutOfLineStorage();
280 return (isTrivialCallback()
281 ? getTrivialCallback()
282 : getNonTrivialCallbacks()->CallPtr
)(CallableAddr
, Params
...);
285 explicit operator bool() const {
286 return (bool)CallbackAndInlineFlag
.getPointer();
290 } // end namespace llvm
292 #endif // LLVM_ADT_FUNCTION_H