• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef LIB_FIT_INCLUDE_LIB_FIT_INTERNAL_FUNCTION_H_
6 #define LIB_FIT_INCLUDE_LIB_FIT_INTERNAL_FUNCTION_H_
7 
8 #include <lib/stdcompat/bit.h>
9 #include <stddef.h>
10 #include <stdlib.h>
11 
12 #include <algorithm>
13 #include <cstring>
14 #include <functional>
15 #include <memory>
16 #include <new>
17 #include <type_traits>
18 #include <utility>
19 
20 #include "../nullable.h"
21 #include "pw_assert/assert.h"
22 #include "pw_preprocessor/compiler.h"
23 
24 namespace fit {
25 namespace internal {
26 
27 // Rounds the first argument up to a non-zero multiple of the second argument.
RoundUpToMultiple(size_t value,size_t multiple)28 constexpr size_t RoundUpToMultiple(size_t value, size_t multiple) {
29   return value == 0 ? multiple : (value + multiple - 1) / multiple * multiple;
30 }
31 
32 // Rounds up to the nearest word. To avoid unnecessary instantiations, function_base can only be
33 // instantiated with an inline size that is a non-zero multiple of the word size.
RoundUpToWord(size_t value)34 constexpr size_t RoundUpToWord(size_t value) { return RoundUpToMultiple(value, sizeof(void*)); }
35 
36 // target_ops is the vtable for the function_base class. The base_target_ops struct holds functions
37 // that are common to all function_base instantiations, regardless of the function's signature.
38 // The derived target_ops template that adds the signature-specific invoke method.
39 //
40 // Splitting the common functions into base_target_ops allows all function_base instantiations to
41 // share the same vtable for their null function instantiation, reducing code size.
42 struct base_target_ops {
43   const void* (*target_type_id)(void* bits, const void* impl_ops);
44   void* (*get)(void* bits);
45   void (*move)(void* from_bits, void* to_bits);
46   void (*destroy)(void* bits);
47 
48  protected:
49   // Aggregate initialization isn't supported with inheritance until C++17, so define a constructor.
base_target_opsbase_target_ops50   constexpr base_target_ops(decltype(target_type_id) target_type_id_func, decltype(get) get_func,
51                             decltype(move) move_func, decltype(destroy) destroy_func)
52       : target_type_id(target_type_id_func),
53         get(get_func),
54         move(move_func),
55         destroy(destroy_func) {}
56 };
57 
58 template <typename Result, typename... Args>
59 struct target_ops final : public base_target_ops {
60   Result (*invoke)(void* bits, Args... args);
61 
target_opsfinal62   constexpr target_ops(decltype(target_type_id) target_type_id_func, decltype(get) get_func,
63                        decltype(move) move_func, decltype(destroy) destroy_func,
64                        decltype(invoke) invoke_func)
65       : base_target_ops(target_type_id_func, get_func, move_func, destroy_func),
66         invoke(invoke_func) {}
67 };
68 
69 static_assert(sizeof(target_ops<void>) == sizeof(void (*)()) * 5, "Unexpected target_ops padding");
70 
71 template <typename Callable, bool is_inline, bool is_shared, typename Result, typename... Args>
72 struct target;
73 
trivial_target_destroy(void *)74 inline void trivial_target_destroy(void* /*bits*/) {}
75 
unshared_target_type_id(void *,const void * impl_ops)76 inline const void* unshared_target_type_id(void* /*bits*/, const void* impl_ops) {
77   return impl_ops;
78 }
79 
80 // vtable for nullptr (empty target function)
81 
82 // All function_base instantiations, regardless of callable type, use the same
83 // vtable for nullptr functions. This avoids generating unnecessary identical
84 // vtables, which reduces code size.
85 //
86 // The null_target class does not need to be a template. However, if it was not
87 // a template, the ops variable would need to be defined in a .cc file for C++14
88 // compatibility. In C++17, null_target::ops could be defined in the class or
89 // elsewhere in the header as an inline variable.
90 template <typename Unused = void>
91 struct null_target {
invokenull_target92   static void invoke(void* /*bits*/) { PW_ASSERT(false); }
93 
94   static const target_ops<void> ops;
95 
96   static_assert(std::is_same<Unused, void>::value, "Only instantiate null_target with void");
97 };
98 
99 template <typename Result, typename... Args>
100 struct target<decltype(nullptr), /*is_inline=*/true, /*is_shared=*/false, Result, Args...> final
101     : public null_target<> {};
102 
103 inline void* null_target_get(void* /*bits*/) { return nullptr; }
104 inline void null_target_move(void* /*from_bits*/, void* /*to_bits*/) {}
105 
106 template <typename Unused>
107 constexpr target_ops<void> null_target<Unused>::ops = {&unshared_target_type_id, &null_target_get,
108                                                        &null_target_move, &trivial_target_destroy,
109                                                        &null_target::invoke};
110 
111 // vtable for inline target function
112 
113 // Trivially movable and destructible types can be moved with a simple memcpy. Use the same function
114 // for all callable types of a particular size to reduce code size.
115 template <size_t size_bytes>
116 inline void inline_trivial_target_move(void* from_bits, void* to_bits) {
117   std::memcpy(to_bits, from_bits, size_bytes);
118 }
119 
120 template <typename Callable, typename Result, typename... Args>
121 struct target<Callable,
122               /*is_inline=*/true, /*is_shared=*/false, Result, Args...>
123     final {
124   template <typename Callable_>
125   static void initialize(void* bits, Callable_&& target) {
126     new (bits) Callable(std::forward<Callable_>(target));
127   }
128   static Result invoke(void* bits, Args... args) {
129     auto& target = *static_cast<Callable*>(bits);
130     return target(std::forward<Args>(args)...);
131   }
132   // Selects which move function to use. Trivially movable and destructible types of a particular
133   // size share a single move function.
134   static constexpr auto get_move_function() {
135     if (std::is_trivially_move_constructible<Callable>::value &&
136         std::is_trivially_destructible<Callable>::value) {
137       return &inline_trivial_target_move<sizeof(Callable)>;
138     }
139     return &move;
140   }
141   // Selects which destroy function to use. Trivially destructible types share a single, empty
142   // destroy function.
143   static constexpr auto get_destroy_function() {
144     return std::is_trivially_destructible<Callable>::value ? &trivial_target_destroy : &destroy;
145   }
146 
147   static const target_ops<Result, Args...> ops;
148 
149  private:
150   static void move(void* from_bits, void* to_bits) {
151     auto& from_target = *static_cast<Callable*>(from_bits);
152     new (to_bits) Callable(std::move(from_target));
153     from_target.~Callable();  // NOLINT(bugprone-use-after-move)
154   }
155   static void destroy(void* bits) {
156     auto& target = *static_cast<Callable*>(bits);
157     target.~Callable();
158   }
159 };
160 
161 inline void* inline_target_get(void* bits) { return bits; }
162 
163 template <typename Callable, typename Result, typename... Args>
164 constexpr target_ops<Result, Args...> target<Callable,
165                                              /*is_inline=*/true,
166                                              /*is_shared=*/false, Result, Args...>::ops = {
167     &unshared_target_type_id, &inline_target_get, target::get_move_function(),
168     target::get_destroy_function(), &target::invoke};
169 
170 // vtable for pointer to target function
171 
172 template <typename Callable, typename Result, typename... Args>
173 struct target<Callable,
174               /*is_inline=*/false, /*is_shared=*/false, Result, Args...>
175     final {
176   template <typename Callable_>
177   static void initialize(void* bits, Callable_&& target) {
178     auto ptr = static_cast<Callable**>(bits);
179     *ptr = new Callable(std::forward<Callable_>(target));
180   }
181   static Result invoke(void* bits, Args... args) {
182     auto& target = **static_cast<Callable**>(bits);
183     return target(std::forward<Args>(args)...);
184   }
185   static void move(void* from_bits, void* to_bits) {
186     auto from_ptr = static_cast<Callable**>(from_bits);
187     auto to_ptr = static_cast<Callable**>(to_bits);
188     *to_ptr = *from_ptr;
189   }
190   static void destroy(void* bits) {
191     auto ptr = static_cast<Callable**>(bits);
192     delete *ptr;
193   }
194 
195   static const target_ops<Result, Args...> ops;
196 };
197 
198 inline void* heap_target_get(void* bits) { return *static_cast<void**>(bits); }
199 
200 template <typename Callable, typename Result, typename... Args>
201 constexpr target_ops<Result, Args...> target<Callable,
202                                              /*is_inline=*/false,
203                                              /*is_shared=*/false, Result, Args...>::ops = {
204     &unshared_target_type_id, &heap_target_get, &target::move, &target::destroy, &target::invoke};
205 
206 // vtable for fit::function std::shared_ptr to target function
207 
208 template <typename SharedFunction>
209 const void* get_target_type_id(const SharedFunction& function_or_callback) {
210   return function_or_callback.target_type_id();
211 }
212 
213 // For this vtable,
214 // Callable by definition will be either a fit::function or fit::callback
215 template <typename SharedFunction, typename Result, typename... Args>
216 struct target<SharedFunction,
217               /*is_inline=*/false, /*is_shared=*/true, Result, Args...>
218     final {
219   static void initialize(void* bits, SharedFunction target) {
220     new (bits) std::shared_ptr<SharedFunction>(
221         std::move(std::make_shared<SharedFunction>(std::move(target))));
222   }
223   static void copy_shared_ptr(void* from_bits, void* to_bits) {
224     auto& from_shared_ptr = *static_cast<std::shared_ptr<SharedFunction>*>(from_bits);
225     new (to_bits) std::shared_ptr<SharedFunction>(from_shared_ptr);
226   }
227   static const void* target_type_id(void* bits, const void* /*impl_ops*/) {
228     auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
229     return ::fit::internal::get_target_type_id(function_or_callback);
230   }
231   static void* get(void* bits) {
232     auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
233     return function_or_callback.template target<SharedFunction>(
234         /*check=*/false);  // void* will fail the check
235   }
236   static Result invoke(void* bits, Args... args) {
237     auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
238     return function_or_callback(std::forward<Args>(args)...);
239   }
240   static void move(void* from_bits, void* to_bits) {
241     auto from_shared_ptr = std::move(*static_cast<std::shared_ptr<SharedFunction>*>(from_bits));
242     new (to_bits) std::shared_ptr<SharedFunction>(std::move(from_shared_ptr));
243   }
244   static void destroy(void* bits) { static_cast<std::shared_ptr<SharedFunction>*>(bits)->reset(); }
245 
246   static const target_ops<Result, Args...> ops;
247 };
248 
249 template <typename SharedFunction, typename Result, typename... Args>
250 constexpr target_ops<Result, Args...> target<SharedFunction,
251                                              /*is_inline=*/false,
252                                              /*is_shared=*/true, Result, Args...>::ops = {
253     &target::target_type_id, &target::get, &target::move, &target::destroy, &target::invoke};
254 
255 // Calculates the alignment to use for a function of the provided
256 // inline_target_size. Some platforms use a large alignment for max_align_t, so
257 // use the minimum of max_align_t and the largest alignment for the inline
258 // target size.
259 //
260 // Alignments must be powers of 2, and alignof(T) <= sizeof(T), so find the
261 // largest power of 2 <= inline_target_size.
262 constexpr size_t FunctionAlignment(size_t inline_target_size) {
263   return std::min(cpp20::bit_floor(inline_target_size), alignof(max_align_t));
264 }
265 
266 // Function implementation details shared by all functions, regardless of
267 // signature. This class is aligned based on inline_target_size and max_align_t
268 // so that the target storage (bits_, the first class member) has correct
269 // alignment.
270 //
271 // See |fit::function| and |fit::callback| documentation for more information.
272 template <size_t inline_target_size>
273 class alignas(FunctionAlignment(inline_target_size)) generic_function_base {
274  public:
275   // The inline target size must be a non-zero multiple of sizeof(void*).  Uses
276   // of |fit::function_impl| and |fit::callback_impl| may call
277   // fit::internal::RoundUpToWord to round to a valid inline size.
278   //
279   // A multiple of sizeof(void*) is required because it:
280   //
281   // - Avoids unnecessary duplicate instantiations of the function classes when
282   //   working with different inline sizes. This reduces code size.
283   // - Prevents creating unnecessarily restrictive functions. Without rounding, a
284   //   function with a non-word size would be padded to at least the next word,
285   //   but that space would be unusable.
286   // - Ensures that the true inline size matches the template parameter, which
287   //   could cause confusion in error messages.
288   //
289   static_assert(inline_target_size >= sizeof(void*),
290                 "The inline target size must be at least one word");
291   static_assert(inline_target_size % sizeof(void*) == 0,
292                 "The inline target size must be a multiple of the word size");
293 
294   // Deleted copy constructor and assign. |generic_function_base|
295   // implementations are move-only.
296   generic_function_base(const generic_function_base& other) = delete;
297   generic_function_base& operator=(const generic_function_base& other) = delete;
298 
299   // Move assignment must be provided by subclasses.
300   generic_function_base& operator=(generic_function_base&& other) = delete;
301 
302  protected:
303   constexpr generic_function_base() : null_bits_(), ops_(&null_target<>::ops) {}
304 
305   generic_function_base(generic_function_base&& other) noexcept { move_target_from(other); }
306 
307   ~generic_function_base() { destroy_target(); }
308 
309   // Returns true if the function has a non-empty target.
310   explicit operator bool() const { return ops_->get(bits_) != nullptr; }
311 
312   // Used by derived "impl" classes to implement operator=().
313   // Assigns an empty target.
314   void assign_null() {
315     destroy_target();
316     initialize_null_target();
317   }
318 
319   // Used by derived "impl" classes to implement operator=().
320   // Assigns the function with a target moved from another function,
321   // leaving the other function with an empty target.
322   void assign_function(generic_function_base&& other) {
323     destroy_target();
324     move_target_from(other);
325   }
326 
327   void swap(generic_function_base& other) {
328     if (&other == this)
329       return;
330 
331     const base_target_ops* temp_ops = ops_;
332     // temp_bits, which stores the target, must maintain the expected alignment.
333     alignas(generic_function_base) uint8_t temp_bits[inline_target_size];
334     ops_->move(bits_, temp_bits);
335 
336     ops_ = other.ops_;
337     other.ops_->move(other.bits_, bits_);
338 
339     other.ops_ = temp_ops;
340     temp_ops->move(temp_bits, other.bits_);
341   }
342 
343   // returns an opaque ID unique to the |Callable| type of the target.
344   // Used by check_target_type.
345   const void* target_type_id() const { return ops_->target_type_id(bits_, ops_); }
346 
347   // leaves target uninitialized
348   void destroy_target() { ops_->destroy(bits_); }
349 
350   // assumes target is uninitialized
351   void initialize_null_target() { ops_ = &null_target<>::ops; }
352 
353   // Gets a pointer to the function context.
354   void* get() const { return ops_->get(bits_); }
355 
356   // Allow function_base to directly access bits_ and ops_ when needed.
357   void* bits() const { return bits_; }
358   const base_target_ops* ops() const { return ops_; }
359   void set_ops(const base_target_ops* new_ops) { ops_ = new_ops; }
360 
361  private:
362   // Implements the move operation, used by move construction and move
363   // assignment. Leaves other target initialized to null.
364   void move_target_from(generic_function_base& other) {
365     ops_ = other.ops_;
366     other.ops_->move(other.bits_, bits_);
367     other.initialize_null_target();
368   }
369 
370   struct empty {};
371 
372   union {
373     // Function context data. The bits_ field requires special alignment, but
374     // adding the alignas() at the field declaration increases the padding.
375     // Instead, generic_function_base is aligned according to max_align_t and
376     // inline_target_size, and bits_ is placed first in the class. Thus, bits_
377     // MUST remain first in the class to ensure proper alignment.
378     mutable uint8_t bits_[inline_target_size];
379 
380     // Empty struct used when initializing the storage in the constexpr
381     // constructor.
382     empty null_bits_;
383   };
384 
385   // The target_ops pointer for this function. This field has lower alignment
386   // requirement than bits, so placing ops after bits allows for better
387   // packing reducing the padding needed in some cases.
388   const base_target_ops* ops_;
389 };
390 
391 template <size_t inline_target_size, bool require_inline, typename FunctionType>
392 class function_base;
393 
394 // Function implementation details that require the function signature.
395 // See |fit::function| and |fit::callback| documentation for more information.
396 template <size_t inline_target_size, bool require_inline, typename Result, typename... Args>
397 class function_base<inline_target_size, require_inline, Result(Args...)>
398     : public generic_function_base<inline_target_size> {
399   using base = generic_function_base<inline_target_size>;
400 
401   // Check alignment and size of the base, which holds the bits_ and ops_ members.
402   static_assert(alignof(base) == FunctionAlignment(inline_target_size),
403                 "Must be aligned as min(alignof(max_align_t), inline_target_size)");
404   static_assert(sizeof(base) == RoundUpToMultiple(inline_target_size + sizeof(base_target_ops*),
405                                                   FunctionAlignment(inline_target_size)),
406                 "generic_function_base has unexpected padding and is not minimal in size");
407 
408   template <typename Callable>
409   using target_type = target<Callable, (sizeof(Callable) <= inline_target_size),
410                              /*is_shared=*/false, Result, Args...>;
411   template <typename SharedFunction>
412   using shared_target_type = target<SharedFunction,
413                                     /*is_inline=*/false,
414                                     /*is_shared=*/true, Result, Args...>;
415 
416   using ops_type = const target_ops<Result, Args...>*;
417 
418  protected:
419   using result_type = Result;
420 
421   constexpr function_base() = default;
422 
423   constexpr function_base(decltype(nullptr)) : function_base() {}
424 
425   function_base(Result (*function_target)(Args...)) { initialize_target(function_target); }
426 
427   template <typename Callable,
428             typename = std::enable_if_t<std::is_convertible<
429                 decltype(std::declval<Callable&>()(std::declval<Args>()...)), result_type>::value>>
430   function_base(Callable&& target) {
431     initialize_target(std::forward<Callable>(target));
432   }
433 
434   function_base(function_base&&) noexcept = default;
435 
436   // Returns a pointer to the function's target.
437   // If |check| is true (the default), the function _may_ abort if the
438   // caller tries to assign the target to a varible of the wrong type. (This
439   // check is currently skipped for share()d objects.)
440   // Note the shared pointer vtable must set |check| to false to assign the
441   // target to |void*|.
442   template <typename Callable>
443   Callable* target(bool check = true) {
444     if (check)
445       check_target_type<Callable>();
446     return static_cast<Callable*>(base::get());
447   }
448 
449   // Returns a pointer to the function's target (const version).
450   // If |check| is true (the default), the function _may_ abort if the
451   // caller tries to assign the target to a varible of the wrong type. (This
452   // check is currently skipped for share()d objects.)
453   // Note the shared pointer vtable must set |check| to false to assign the
454   // target to |void*|.
455   template <typename Callable>
456   const Callable* target(bool check = true) const {
457     if (check)
458       check_target_type<Callable>();
459     return static_cast<Callable*>(base::get());
460   }
461 
462   // Used by the derived "impl" classes to implement share().
463   //
464   // The caller creates a new object of the same type as itself, and passes in
465   // the empty object. This function first checks if |this| is already shared,
466   // and if not, creates a new version of itself containing a |std::shared_ptr|
467   // to its original self, and updates |ops_| to the vtable for the shared
468   // version.
469   //
470   // Then it copies its |shared_ptr| to the |bits_| of the given |copy|, and
471   // assigns the same shared pointer vtable to the copy's |ops_|.
472   //
473   // The target itself is not copied; it is moved to the heap and its lifetime
474   // is extended until all references have been released.
475   //
476   // Note: This method is not supported on |fit::inline_function<>|
477   //       because it may incur a heap allocation which is contrary to
478   //       the stated purpose of |fit::inline_function<>|.
479   template <typename SharedFunction>
480   void share_with(SharedFunction& copy) {
481     static_assert(!require_inline, "Inline functions cannot be shared.");
482     if (base::get() != nullptr) {
483       // Convert to a shared function if it isn't already.
484       if (base::ops() != &shared_target_type<SharedFunction>::ops) {
485         shared_target_type<SharedFunction>::initialize(
486             base::bits(), std::move(*static_cast<SharedFunction*>(this)));
487         base::set_ops(&shared_target_type<SharedFunction>::ops);
488       }
489       copy_shared_target_to(copy);
490     }
491   }
492 
493   // Used by derived "impl" classes to implement operator()().
494   // Invokes the function's target.
495   // Note that fit::callback will release the target immediately after
496   // invoke() (also affecting any share()d copies).
497   // Aborts if the function's target is empty.
498   // TODO(b/241567321): Remove "no sanitize" after pw_protobuf is fixed.
499   Result invoke(Args... args) const PW_NO_SANITIZE("function") {
500     // Down cast the ops to the derived type that this function was instantiated
501     // with, which includes the invoke function.
502     //
503     // NOTE: This abuses the calling convention when invoking a null function
504     // that takes arguments! Null functions share a single vtable with a void()
505     // invoke function. This is permitted only because invoking a null function
506     // is an error that immediately aborts execution. Also, the null invoke
507     // function never attempts to access any passed arguments.
508     return static_cast<ops_type>(base::ops())->invoke(base::bits(), std::forward<Args>(args)...);
509   }
510 
511   // Used by derived "impl" classes to implement operator=().
512   // Assigns the function's target.
513   // If target == nullptr, assigns an empty target.
514   template <typename Callable,
515             typename = std::enable_if_t<std::is_convertible<
516                 decltype(std::declval<Callable&>()(std::declval<Args>()...)), result_type>::value>>
517   void assign_callable(Callable&& target) {
518     base::destroy_target();
519     initialize_target(std::forward<Callable>(target));
520   }
521 
522  private:
523   // fit::function and fit::callback are not directly copyable, but share()
524   // will create shared references to the original object. This method
525   // implements the copy operation for the |std::shared_ptr| wrapper.
526   template <typename SharedFunction>
527   void copy_shared_target_to(SharedFunction& copy) {
528     copy.destroy_target();
529     PW_ASSERT(base::ops() == &shared_target_type<SharedFunction>::ops);
530     shared_target_type<SharedFunction>::copy_shared_ptr(base::bits(), copy.bits());
531     copy.set_ops(base::ops());
532   }
533 
534   // target may or may not be initialized.
535   template <typename Callable>
536   void initialize_target(Callable&& target) {
537     // Convert function or function references to function pointer.
538     using DecayedCallable = std::decay_t<Callable>;
539     static_assert(!require_inline || alignof(DecayedCallable) <= alignof(base),
540                   "Alignment of Callable must be <= alignment of the function class.");
541     static_assert(!require_inline || sizeof(DecayedCallable) <= inline_target_size,
542                   "Callable too large to store inline as requested.");
543     if (is_null(target)) {
544       base::initialize_null_target();
545     } else {
546       base::set_ops(&target_type<DecayedCallable>::ops);
547       target_type<DecayedCallable>::initialize(base::bits(), std::forward<Callable>(target));
548     }
549   }
550 
551   // Called by target() if |check| is true.
552   // Checks the template parameter, usually inferred from the context of
553   // the call to target(), and aborts the program if it can determine that
554   // the Callable type is not compatible with the function's Result and Args.
555   template <typename Callable>
556   void check_target_type() const {
557     if (target_type<Callable>::ops.target_type_id(nullptr, &target_type<Callable>::ops) !=
558         base::target_type_id()) {
559       PW_ASSERT(false);
560     }
561   }
562 };
563 
564 }  // namespace internal
565 }  // namespace fit
566 
567 #endif  // LIB_FIT_INCLUDE_LIB_FIT_INTERNAL_FUNCTION_H_
568