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