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