• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright 2020 The WebRTC Project Authors. All rights reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #ifndef RTC_BASE_UNTYPED_FUNCTION_H_
12 #define RTC_BASE_UNTYPED_FUNCTION_H_
13 
14 #include <cstddef>
15 #include <cstring>
16 #include <memory>
17 #include <type_traits>
18 #include <utility>
19 
20 #include "rtc_base/system/assume.h"
21 
22 namespace webrtc {
23 namespace webrtc_function_impl {
24 
25 using FunVoid = void();
26 
27 // Inline storage size is this many machine words.
28 enum : size_t { kInlineStorageWords = 4 };
29 
30 union VoidUnion {
31   void* void_ptr;
32   FunVoid* fun_ptr;
33   typename std::aligned_storage<kInlineStorageWords * sizeof(uintptr_t)>::type
34       inline_storage;
35 };
36 
37 // Returns the number of elements of the `inline_storage` array required to
38 // store an object of type T.
39 template <typename T>
InlineStorageSize()40 constexpr size_t InlineStorageSize() {
41   // sizeof(T) / sizeof(uintptr_t), but rounded up.
42   return (sizeof(T) + sizeof(uintptr_t) - 1) / sizeof(uintptr_t);
43 }
44 
45 template <typename T>
46 struct CallHelpers;
47 template <typename RetT, typename... ArgT>
48 struct CallHelpers<RetT(ArgT...)> {
49   // Return type of the three helpers below.
50   using return_type = RetT;
51   // Complete function type of the three helpers below.
52   using function_type = RetT(VoidUnion*, ArgT...);
53   // Helper for calling the `void_ptr` case of VoidUnion.
54   template <typename F>
55   static RetT CallVoidPtr(VoidUnion* vu, ArgT... args) {
56     return (*static_cast<F*>(vu->void_ptr))(std::forward<ArgT>(args)...);
57   }
58   // Helper for calling the `fun_ptr` case of VoidUnion.
59   static RetT CallFunPtr(VoidUnion* vu, ArgT... args) {
60     return (reinterpret_cast<RetT (*)(ArgT...)>(vu->fun_ptr))(
61         std::forward<ArgT>(args)...);
62   }
63   // Helper for calling the `inline_storage` case of VoidUnion.
64   template <typename F>
65   static RetT CallInlineStorage(VoidUnion* vu, ArgT... args) {
66     return (*reinterpret_cast<F*>(&vu->inline_storage))(
67         std::forward<ArgT>(args)...);
68   }
69 };
70 
71 }  // namespace webrtc_function_impl
72 
73 // A class that holds (and owns) any callable. The same function call signature
74 // must be provided when constructing and calling the object.
75 //
76 // The point of not having the call signature as a class template parameter is
77 // to have one single concrete type for all signatures; this reduces binary
78 // size.
79 class UntypedFunction final {
80  public:
81   // Callables of at most this size can be stored inline, if they are trivial.
82   // (Useful in tests and benchmarks; avoid using this in production code.)
83   enum : size_t {
84     kInlineStorageSize = sizeof(webrtc_function_impl::VoidUnion::inline_storage)
85   };
86   static_assert(kInlineStorageSize ==
87                     webrtc_function_impl::kInlineStorageWords *
88                         sizeof(uintptr_t),
89                 "");
90 
91   // The *UntypedFunctionArgs structs are used to transfer arguments from
92   // PrepareArgs() to Create(). They are trivial, but may own heap allocations,
93   // so make sure to pass them to Create() exactly once!
94   //
95   // The point of doing Create(PrepareArgs(foo)) instead of just Create(foo) is
96   // to separate the code that has to be inlined (PrepareArgs) from the code
97   // that can be noninlined (Create); the *UntypedFunctionArgs types are
98   // designed to efficiently carry the required information from one to the
99   // other.
100   template <size_t N>
101   struct TrivialUntypedFunctionArgs {
102     static_assert(N >= 1, "");
103     static_assert(N <= webrtc_function_impl::kInlineStorageWords, "");
104     // We use an uintptr_t array here instead of std::aligned_storage, because
105     // the former can be efficiently passed in registers when using
106     // TrivialUntypedFunctionArgs as a function argument. (We can't do the same
107     // in VoidUnion, because std::aligned_storage but not uintptr_t can be
108     // legally reinterpret_casted to arbitrary types.
109     // TrivialUntypedFunctionArgs, on the other hand, only needs to handle
110     // placement new and memcpy.)
111     alignas(std::max_align_t) uintptr_t inline_storage[N];
112     webrtc_function_impl::FunVoid* call;
113   };
114   struct NontrivialUntypedFunctionArgs {
115     void* void_ptr;
116     webrtc_function_impl::FunVoid* call;
117     void (*del)(webrtc_function_impl::VoidUnion*);
118   };
119   struct FunctionPointerUntypedFunctionArgs {
120     webrtc_function_impl::FunVoid* fun_ptr;
121     webrtc_function_impl::FunVoid* call;
122   };
123 
124   // Create function for lambdas and other callables that are trivial and small;
125   // it accepts every type of argument except those noted in its enable_if call.
126   template <
127       typename Signature,
128       typename F,
129       typename F_deref = typename std::remove_reference<F>::type,
130       typename std::enable_if<
131           // Not for function pointers; we have another overload for that below.
132           !std::is_function<
133               typename std::remove_pointer<F_deref>::type>::value &&
134 
135           // Not for nullptr; we have a constructor for that below.
136           !std::is_same<std::nullptr_t,
137                         typename std::remove_cv<F>::type>::value &&
138 
139           // Not for UntypedFunction objects; use move construction or
140           // assignment.
141           !std::is_same<UntypedFunction,
142                         typename std::remove_cv<F_deref>::type>::value &&
143 
144           // Only for trivial callables that will fit in inline storage.
145           std::is_trivially_move_constructible<F_deref>::value &&
146           std::is_trivially_destructible<F_deref>::value &&
147           sizeof(F_deref) <= kInlineStorageSize>::type* = nullptr,
148       size_t InlineSize = webrtc_function_impl::InlineStorageSize<F_deref>()>
149   static TrivialUntypedFunctionArgs<InlineSize> PrepareArgs(F&& f) {
150     // The callable is trivial and small enough, so we just store its bytes
151     // in the inline storage.
152     TrivialUntypedFunctionArgs<InlineSize> args;
153     new (&args.inline_storage) F_deref(std::forward<F>(f));
154     args.call = reinterpret_cast<webrtc_function_impl::FunVoid*>(
155         webrtc_function_impl::CallHelpers<
156             Signature>::template CallInlineStorage<F_deref>);
157     return args;
158   }
159   template <size_t InlineSize>
160   static UntypedFunction Create(TrivialUntypedFunctionArgs<InlineSize> args) {
161     webrtc_function_impl::VoidUnion vu;
162     std::memcpy(&vu.inline_storage, args.inline_storage,
163                 sizeof(args.inline_storage));
164     return UntypedFunction(vu, args.call, nullptr);
165   }
166 
167   // Create function for lambdas and other callables that are nontrivial or
168   // large; it accepts every type of argument except those noted in its
169   // enable_if call.
170   template <typename Signature,
171             typename F,
172             typename F_deref = typename std::remove_reference<F>::type,
173             typename std::enable_if<
174                 // Not for function pointers; we have another overload for that
175                 // below.
176                 !std::is_function<
177                     typename std::remove_pointer<F_deref>::type>::value &&
178 
179                 // Not for nullptr; we have a constructor for that below.
180                 !std::is_same<std::nullptr_t,
181                               typename std::remove_cv<F>::type>::value &&
182 
183                 // Not for UntypedFunction objects; use move construction or
184                 // assignment.
185                 !std::is_same<UntypedFunction,
186                               typename std::remove_cv<F_deref>::type>::value &&
187 
188                 // Only for nontrivial callables, or callables that won't fit in
189                 // inline storage.
190                 !(std::is_trivially_move_constructible<F_deref>::value &&
191                   std::is_trivially_destructible<F_deref>::value &&
192                   sizeof(F_deref) <= kInlineStorageSize)>::type* = nullptr>
193   static NontrivialUntypedFunctionArgs PrepareArgs(F&& f) {
194     // The callable is either nontrivial or too large, so we can't keep it
195     // in the inline storage; use the heap instead.
196     NontrivialUntypedFunctionArgs args;
197     args.void_ptr = new F_deref(std::forward<F>(f));
198     args.call = reinterpret_cast<webrtc_function_impl::FunVoid*>(
199         webrtc_function_impl::CallHelpers<Signature>::template CallVoidPtr<
200             F_deref>);
201     args.del = static_cast<void (*)(webrtc_function_impl::VoidUnion*)>(
202         [](webrtc_function_impl::VoidUnion* vu) {
203           // Assuming that this pointer isn't null allows the
204           // compiler to eliminate a null check in the (inlined)
205           // delete operation.
206           RTC_ASSUME(vu->void_ptr != nullptr);
207           delete reinterpret_cast<F_deref*>(vu->void_ptr);
208         });
209     return args;
210   }
211   static UntypedFunction Create(NontrivialUntypedFunctionArgs args) {
212     webrtc_function_impl::VoidUnion vu;
213     vu.void_ptr = args.void_ptr;
214     return UntypedFunction(vu, args.call, args.del);
215   }
216 
217   // Create function that accepts function pointers. If the argument is null,
218   // the result is an empty UntypedFunction.
219   template <typename Signature>
220   static FunctionPointerUntypedFunctionArgs PrepareArgs(Signature* f) {
221     FunctionPointerUntypedFunctionArgs args;
222     args.fun_ptr = reinterpret_cast<webrtc_function_impl::FunVoid*>(f);
223     args.call = reinterpret_cast<webrtc_function_impl::FunVoid*>(
224         webrtc_function_impl::CallHelpers<Signature>::CallFunPtr);
225     return args;
226   }
227   static UntypedFunction Create(FunctionPointerUntypedFunctionArgs args) {
228     webrtc_function_impl::VoidUnion vu;
229     vu.fun_ptr = args.fun_ptr;
230     return UntypedFunction(vu, args.fun_ptr == nullptr ? nullptr : args.call,
231                            nullptr);
232   }
233 
234   // Prepares arguments and creates an UntypedFunction in one go.
235   template <typename Signature, typename F>
236   static UntypedFunction Create(F&& f) {
237     return Create(PrepareArgs<Signature>(std::forward<F>(f)));
238   }
239 
240   // Default constructor. Creates an empty UntypedFunction.
241   UntypedFunction() : call_(nullptr), delete_(nullptr) {}
242 
243   // Nullptr constructor and assignment. Creates an empty UntypedFunction.
244   UntypedFunction(std::nullptr_t)  // NOLINT(runtime/explicit)
245       : call_(nullptr), delete_(nullptr) {}
246   UntypedFunction& operator=(std::nullptr_t) {
247     call_ = nullptr;
248     if (delete_) {
249       delete_(&f_);
250       delete_ = nullptr;
251     }
252     return *this;
253   }
254 
255   // Not copyable.
256   UntypedFunction(const UntypedFunction&) = delete;
257   UntypedFunction& operator=(const UntypedFunction&) = delete;
258 
259   // Move construction and assignment.
260   UntypedFunction(UntypedFunction&& other)
261       : f_(other.f_), call_(other.call_), delete_(other.delete_) {
262     other.delete_ = nullptr;
263   }
264   UntypedFunction& operator=(UntypedFunction&& other) {
265     if (delete_) {
266       delete_(&f_);
267     }
268     f_ = other.f_;
269     call_ = other.call_;
270     delete_ = other.delete_;
271     other.delete_ = nullptr;
272     return *this;
273   }
274 
275   ~UntypedFunction() {
276     if (delete_) {
277       delete_(&f_);
278     }
279   }
280 
281   friend void swap(UntypedFunction& a, UntypedFunction& b) {
282     using std::swap;
283     swap(a.f_, b.f_);
284     swap(a.call_, b.call_);
285     swap(a.delete_, b.delete_);
286   }
287 
288   // Returns true if we have a function, false if we don't (i.e., we're null).
289   explicit operator bool() const { return call_ != nullptr; }
290 
291   template <typename Signature, typename... ArgT>
292   typename webrtc_function_impl::CallHelpers<Signature>::return_type Call(
293       ArgT&&... args) {
294     return reinterpret_cast<
295         typename webrtc_function_impl::CallHelpers<Signature>::function_type*>(
296         call_)(&f_, std::forward<ArgT>(args)...);
297   }
298 
299   // Returns true iff we don't need to call a destructor. This is guaranteed
300   // to hold for a moved-from object.
301   bool IsTriviallyDestructible() { return delete_ == nullptr; }
302 
303  private:
304   UntypedFunction(webrtc_function_impl::VoidUnion f,
305                   webrtc_function_impl::FunVoid* call,
306                   void (*del)(webrtc_function_impl::VoidUnion*))
307       : f_(f), call_(call), delete_(del) {}
308 
309   // The callable thing, or a pointer to it.
310   webrtc_function_impl::VoidUnion f_;
311 
312   // Pointer to a dispatch function that knows the type of the callable thing
313   // that's stored in f_, and how to call it. An UntypedFunction object is empty
314   // (null) iff call_ is null.
315   webrtc_function_impl::FunVoid* call_;
316 
317   // Pointer to a function that knows how to delete the callable thing that's
318   // stored in f_. Null if `f_` is trivially deletable.
319   void (*delete_)(webrtc_function_impl::VoidUnion*);
320 };
321 
322 }  // namespace webrtc
323 
324 #endif  // RTC_BASE_UNTYPED_FUNCTION_H_
325