blob: 4cf078dc2a008b2f98f0c03a77170cd2db0a5b06 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_FIT_INTERNAL_FUNCTION_H_
#define LIB_FIT_INTERNAL_FUNCTION_H_
#include <lib/stdcompat/bit.h>
#include <stddef.h>
#include <stdlib.h>
#include <algorithm>
#include <cstring>
#include <functional>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>
#include "../nullable.h"
#include "pw_assert/assert.h"
#include "pw_preprocessor/compiler.h"
namespace fit {
namespace internal {
// Rounds the first argument up to a non-zero multiple of the second argument.
constexpr size_t RoundUpToMultiple(size_t value, size_t multiple) {
return value == 0 ? multiple : (value + multiple - 1) / multiple * multiple;
}
// Rounds up to the nearest word. To avoid unnecessary instantiations, function_base can only be
// instantiated with an inline size that is a non-zero multiple of the word size.
constexpr size_t RoundUpToWord(size_t value) { return RoundUpToMultiple(value, sizeof(void*)); }
// target_ops is the vtable for the function_base class. The base_target_ops struct holds functions
// that are common to all function_base instantiations, regardless of the function's signature.
// The derived target_ops template that adds the signature-specific invoke method.
//
// Splitting the common functions into base_target_ops allows all function_base instantiations to
// share the same vtable for their null function instantiation, reducing code size.
struct base_target_ops {
const void* (*target_type_id)(void* bits, const void* impl_ops);
void* (*get)(void* bits);
void (*move)(void* from_bits, void* to_bits);
void (*destroy)(void* bits);
protected:
// Aggregate initialization isn't supported with inheritance until C++17, so define a constructor.
constexpr base_target_ops(decltype(target_type_id) target_type_id_func, decltype(get) get_func,
decltype(move) move_func, decltype(destroy) destroy_func)
: target_type_id(target_type_id_func),
get(get_func),
move(move_func),
destroy(destroy_func) {}
};
template <typename Result, typename... Args>
struct target_ops final : public base_target_ops {
Result (*invoke)(void* bits, Args... args);
constexpr target_ops(decltype(target_type_id) target_type_id_func, decltype(get) get_func,
decltype(move) move_func, decltype(destroy) destroy_func,
decltype(invoke) invoke_func)
: base_target_ops(target_type_id_func, get_func, move_func, destroy_func),
invoke(invoke_func) {}
};
static_assert(sizeof(target_ops<void>) == sizeof(void (*)()) * 5, "Unexpected target_ops padding");
template <typename Callable, bool is_inline, bool is_shared, typename Allocator, typename Result,
typename... Args>
struct target;
inline void trivial_target_destroy(void* /*bits*/) {}
inline const void* unshared_target_type_id(void* /*bits*/, const void* impl_ops) {
return impl_ops;
}
// vtable for nullptr (empty target function)
// All function_base instantiations, regardless of callable type, use the same
// vtable for nullptr functions. This avoids generating unnecessary identical
// vtables, which reduces code size.
//
// The null_target class does not need to be a template. However, if it was not
// a template, the ops variable would need to be defined in a .cc file for C++14
// compatibility. In C++17, null_target::ops could be defined in the class or
// elsewhere in the header as an inline variable.
template <typename Unused = void>
struct null_target {
static void invoke(void* /*bits*/) { PW_ASSERT(false); }
static const target_ops<void> ops;
static_assert(std::is_same<Unused, void>::value, "Only instantiate null_target with void");
};
template <typename Allocator, typename Result, typename... Args>
struct target<decltype(nullptr), /*is_inline=*/true, /*is_shared=*/false, Allocator, Result,
Args...>
final : public null_target<> {};
inline void* null_target_get(void* /*bits*/) { return nullptr; }
inline void null_target_move(void* /*from_bits*/, void* /*to_bits*/) {}
template <typename Unused>
constexpr target_ops<void> null_target<Unused>::ops = {&unshared_target_type_id, &null_target_get,
&null_target_move, &trivial_target_destroy,
&null_target::invoke};
// vtable for inline target function
// Trivially movable and destructible types can be moved with a simple memcpy. Use the same function
// for all callable types of a particular size to reduce code size.
template <size_t size_bytes>
inline void inline_trivial_target_move(void* from_bits, void* to_bits) {
std::memcpy(to_bits, from_bits, size_bytes);
}
template <typename Callable, typename Allocator, typename Result, typename... Args>
struct target<Callable, /*is_inline=*/true, /*is_shared=*/false, Allocator, Result, Args...> final {
template <typename Callable_>
static void initialize(void* bits, Callable_&& target) {
new (bits) Callable(std::forward<Callable_>(target));
}
static Result invoke(void* bits, Args... args) {
auto& target = *static_cast<Callable*>(bits);
return target(std::forward<Args>(args)...);
}
// Selects which move function to use. Trivially movable and destructible types of a particular
// size share a single move function.
static constexpr auto get_move_function() {
if (std::is_trivially_move_constructible<Callable>::value &&
std::is_trivially_destructible<Callable>::value) {
return &inline_trivial_target_move<sizeof(Callable)>;
}
return &move;
}
// Selects which destroy function to use. Trivially destructible types share a single, empty
// destroy function.
static constexpr auto get_destroy_function() {
return std::is_trivially_destructible<Callable>::value ? &trivial_target_destroy : &destroy;
}
static const target_ops<Result, Args...> ops;
private:
static void move(void* from_bits, void* to_bits) {
auto& from_target = *static_cast<Callable*>(from_bits);
new (to_bits) Callable(std::move(from_target));
from_target.~Callable(); // NOLINT(bugprone-use-after-move)
}
static void destroy(void* bits) {
auto& target = *static_cast<Callable*>(bits);
target.~Callable();
}
};
inline void* inline_target_get(void* bits) { return bits; }
template <typename Callable, typename Allocator, typename Result, typename... Args>
constexpr target_ops<Result, Args...>
target<Callable, /*is_inline=*/true, /*is_shared=*/false, Allocator, Result, Args...>::ops = {
&unshared_target_type_id, &inline_target_get, target::get_move_function(),
target::get_destroy_function(), &target::invoke};
// vtable for pointer to target function
template <typename Callable, typename Allocator, typename Result, typename... Args>
struct target<Callable, /*is_inline=*/false, /*is_shared=*/false, Allocator, Result, Args...>
final {
template <typename Callable_>
static void initialize(void* bits, Callable_&& target) {
auto ptr = static_cast<Callable**>(bits);
CallableAllocator allocator;
*ptr = CallableAllocatorTraits::allocate(allocator, 1u);
if (*ptr) {
CallableAllocatorTraits::construct(allocator, *ptr, std::forward<Callable_>(target));
}
}
static Result invoke(void* bits, Args... args) {
auto& target = **static_cast<Callable**>(bits);
return target(std::forward<Args>(args)...);
}
static void move(void* from_bits, void* to_bits) {
auto from_ptr = static_cast<Callable**>(from_bits);
auto to_ptr = static_cast<Callable**>(to_bits);
*to_ptr = *from_ptr;
}
static void destroy(void* bits) {
auto ptr = static_cast<Callable**>(bits);
if (*ptr) {
CallableAllocator allocator;
CallableAllocatorTraits::destroy(allocator, *ptr);
CallableAllocatorTraits::deallocate(allocator, *ptr, 1u);
*ptr = nullptr;
}
}
static const target_ops<Result, Args...> ops;
private:
using AllocatorTraits = std::allocator_traits<Allocator>;
using CallableAllocator = typename AllocatorTraits::template rebind_alloc<Callable>;
using CallableAllocatorTraits = std::allocator_traits<CallableAllocator>;
static_assert(CallableAllocatorTraits::is_always_equal::value,
"Objects of type Allocator must always be equal to each other: an Allocator object "
"must be able to deallocate the memory allocated by a different Allocator object.");
};
inline void* heap_target_get(void* bits) { return *static_cast<void**>(bits); }
template <typename Callable, typename Allocator, typename Result, typename... Args>
constexpr target_ops<Result, Args...>
target<Callable, /*is_inline=*/false, /*is_shared=*/false, Allocator, Result, Args...>::ops = {
&unshared_target_type_id, &heap_target_get, &target::move, &target::destroy,
&target::invoke};
// vtable for fit::function std::shared_ptr to target function
template <typename SharedFunction>
const void* get_target_type_id(const SharedFunction& function_or_callback) {
return function_or_callback.target_type_id();
}
// For this vtable,
// Callable by definition will be either a fit::function or fit::callback
template <typename SharedFunction, typename Allocator, typename Result, typename... Args>
struct target<SharedFunction, /*is_inline=*/false, /*is_shared=*/true, Allocator, Result, Args...>
final {
static void initialize(void* bits, SharedFunction target) {
new (bits) std::shared_ptr<SharedFunction>(
std::move(std::allocate_shared<SharedFunction, Allocator>(Allocator(), std::move(target))));
}
static void copy_shared_ptr(void* from_bits, void* to_bits) {
auto& from_shared_ptr = *static_cast<std::shared_ptr<SharedFunction>*>(from_bits);
new (to_bits) std::shared_ptr<SharedFunction>(from_shared_ptr);
}
static const void* target_type_id(void* bits, const void* /*impl_ops*/) {
auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
return ::fit::internal::get_target_type_id(function_or_callback);
}
static void* get(void* bits) {
auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
return function_or_callback.template target<SharedFunction>(
/*check=*/false); // void* will fail the check
}
static Result invoke(void* bits, Args... args) {
auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
return function_or_callback(std::forward<Args>(args)...);
}
static void move(void* from_bits, void* to_bits) {
auto from_shared_ptr = std::move(*static_cast<std::shared_ptr<SharedFunction>*>(from_bits));
new (to_bits) std::shared_ptr<SharedFunction>(std::move(from_shared_ptr));
}
static void destroy(void* bits) { static_cast<std::shared_ptr<SharedFunction>*>(bits)->reset(); }
static const target_ops<Result, Args...> ops;
};
template <typename SharedFunction, typename Allocator, typename Result, typename... Args>
constexpr target_ops<Result, Args...> target<
SharedFunction, /*is_inline=*/false, /*is_shared=*/true, Allocator, Result, Args...>::ops = {
&target::target_type_id, &target::get, &target::move, &target::destroy, &target::invoke};
// Calculates the alignment to use for a function of the provided
// inline_target_size. Some platforms use a large alignment for max_align_t, so
// use the minimum of max_align_t and the largest alignment for the inline
// target size.
//
// Alignments must be powers of 2, and alignof(T) <= sizeof(T), so find the
// largest power of 2 <= inline_target_size.
constexpr size_t FunctionAlignment(size_t inline_target_size) {
return std::min(cpp20::bit_floor(inline_target_size), alignof(max_align_t));
}
// Function implementation details shared by all functions, regardless of
// signature. This class is aligned based on inline_target_size and max_align_t
// so that the target storage (bits_, the first class member) has correct
// alignment.
//
// See |fit::function| and |fit::callback| documentation for more information.
template <size_t inline_target_size>
class alignas(FunctionAlignment(inline_target_size)) generic_function_base {
public:
// The inline target size must be a non-zero multiple of sizeof(void*). Uses
// of |fit::function_impl| and |fit::callback_impl| may call
// fit::internal::RoundUpToWord to round to a valid inline size.
//
// A multiple of sizeof(void*) is required because it:
//
// - Avoids unnecessary duplicate instantiations of the function classes when
// working with different inline sizes. This reduces code size.
// - Prevents creating unnecessarily restrictive functions. Without rounding, a
// function with a non-word size would be padded to at least the next word,
// but that space would be unusable.
// - Ensures that the true inline size matches the template parameter, which
// could cause confusion in error messages.
//
static_assert(inline_target_size >= sizeof(void*),
"The inline target size must be at least one word");
static_assert(inline_target_size % sizeof(void*) == 0,
"The inline target size must be a multiple of the word size");
// Deleted copy constructor and assign. |generic_function_base|
// implementations are move-only.
generic_function_base(const generic_function_base& other) = delete;
generic_function_base& operator=(const generic_function_base& other) = delete;
// Move assignment must be provided by subclasses.
generic_function_base& operator=(generic_function_base&& other) = delete;
protected:
constexpr generic_function_base() : null_bits_(), ops_(&null_target<>::ops) {}
generic_function_base(generic_function_base&& other) noexcept { move_target_from(other); }
~generic_function_base() { destroy_target(); }
// Returns true if the function has a non-empty target.
explicit operator bool() const { return ops_->get(bits_) != nullptr; }
// Used by derived "impl" classes to implement operator=().
// Assigns an empty target.
void assign_null() {
destroy_target();
initialize_null_target();
}
// Used by derived "impl" classes to implement operator=().
// Assigns the function with a target moved from another function,
// leaving the other function with an empty target.
void assign_function(generic_function_base&& other) {
destroy_target();
move_target_from(other);
}
void swap(generic_function_base& other) {
if (&other == this)
return;
const base_target_ops* temp_ops = ops_;
// temp_bits, which stores the target, must maintain the expected alignment.
alignas(generic_function_base) uint8_t temp_bits[inline_target_size];
ops_->move(bits_, temp_bits);
ops_ = other.ops_;
other.ops_->move(other.bits_, bits_);
other.ops_ = temp_ops;
temp_ops->move(temp_bits, other.bits_);
}
// returns an opaque ID unique to the |Callable| type of the target.
// Used by check_target_type.
const void* target_type_id() const { return ops_->target_type_id(bits_, ops_); }
// leaves target uninitialized
void destroy_target() { ops_->destroy(bits_); }
// assumes target is uninitialized
void initialize_null_target() { ops_ = &null_target<>::ops; }
// Gets a pointer to the function context.
void* get() const { return ops_->get(bits_); }
// Allow function_base to directly access bits_ and ops_ when needed.
void* bits() const { return bits_; }
const base_target_ops* ops() const { return ops_; }
void set_ops(const base_target_ops* new_ops) { ops_ = new_ops; }
private:
// Implements the move operation, used by move construction and move
// assignment. Leaves other target initialized to null.
void move_target_from(generic_function_base& other) {
ops_ = other.ops_;
other.ops_->move(other.bits_, bits_);
other.initialize_null_target();
}
struct empty {};
union {
// Function context data. The bits_ field requires special alignment, but
// adding the alignas() at the field declaration increases the padding.
// Instead, generic_function_base is aligned according to max_align_t and
// inline_target_size, and bits_ is placed first in the class. Thus, bits_
// MUST remain first in the class to ensure proper alignment.
mutable uint8_t bits_[inline_target_size];
// Empty struct used when initializing the storage in the constexpr
// constructor.
empty null_bits_;
};
// The target_ops pointer for this function. This field has lower alignment
// requirement than bits, so placing ops after bits allows for better
// packing reducing the padding needed in some cases.
const base_target_ops* ops_;
};
template <size_t inline_target_size, bool require_inline, typename FunctionType, typename Allocator>
class function_base;
// Function implementation details that require the function signature.
// See |fit::function| and |fit::callback| documentation for more information.
template <size_t inline_target_size, bool require_inline, typename Allocator, typename Result,
typename... Args>
class function_base<inline_target_size, require_inline, Result(Args...), Allocator>
: public generic_function_base<inline_target_size> {
using base = generic_function_base<inline_target_size>;
// Check alignment and size of the base, which holds the bits_ and ops_ members.
static_assert(alignof(base) == FunctionAlignment(inline_target_size),
"Must be aligned as min(alignof(max_align_t), inline_target_size)");
static_assert(sizeof(base) == RoundUpToMultiple(inline_target_size + sizeof(base_target_ops*),
FunctionAlignment(inline_target_size)),
"generic_function_base has unexpected padding and is not minimal in size");
template <typename Callable>
using target_type = target<Callable, (sizeof(Callable) <= inline_target_size),
/*is_shared=*/false, Allocator, Result, Args...>;
template <typename SharedFunction>
using shared_target_type =
target<SharedFunction, /*is_inline=*/false, /*is_shared=*/true, Allocator, Result, Args...>;
using ops_type = const target_ops<Result, Args...>*;
protected:
using result_type = Result;
constexpr function_base() = default;
constexpr function_base(decltype(nullptr)) : function_base() {}
function_base(Result (*function_target)(Args...)) { initialize_target(function_target); }
template <typename Callable,
typename = std::enable_if_t<std::is_convertible<
decltype(std::declval<Callable&>()(std::declval<Args>()...)), result_type>::value>>
function_base(Callable&& target) {
initialize_target(std::forward<Callable>(target));
}
function_base(function_base&&) noexcept = default;
// Returns a pointer to the function's target.
// If |check| is true (the default), the function _may_ abort if the
// caller tries to assign the target to a varible of the wrong type. (This
// check is currently skipped for share()d objects.)
// Note the shared pointer vtable must set |check| to false to assign the
// target to |void*|.
template <typename Callable>
Callable* target(bool check = true) {
if (check)
check_target_type<Callable>();
return static_cast<Callable*>(base::get());
}
// Returns a pointer to the function's target (const version).
// If |check| is true (the default), the function _may_ abort if the
// caller tries to assign the target to a varible of the wrong type. (This
// check is currently skipped for share()d objects.)
// Note the shared pointer vtable must set |check| to false to assign the
// target to |void*|.
template <typename Callable>
const Callable* target(bool check = true) const {
if (check)
check_target_type<Callable>();
return static_cast<Callable*>(base::get());
}
// Used by the derived "impl" classes to implement share().
//
// The caller creates a new object of the same type as itself, and passes in
// the empty object. This function first checks if |this| is already shared,
// and if not, creates a new version of itself containing a |std::shared_ptr|
// to its original self, and updates |ops_| to the vtable for the shared
// version.
//
// Then it copies its |shared_ptr| to the |bits_| of the given |copy|, and
// assigns the same shared pointer vtable to the copy's |ops_|.
//
// The target itself is not copied; it is moved to the heap and its lifetime
// is extended until all references have been released.
//
// Note: This method is not supported on |fit::inline_function<>|
// because it may incur a heap allocation which is contrary to
// the stated purpose of |fit::inline_function<>|.
template <typename SharedFunction>
void share_with(SharedFunction& copy) {
static_assert(!require_inline, "Inline functions cannot be shared.");
if (base::get() != nullptr) {
// Convert to a shared function if it isn't already.
if (base::ops() != &shared_target_type<SharedFunction>::ops) {
shared_target_type<SharedFunction>::initialize(
base::bits(), std::move(*static_cast<SharedFunction*>(this)));
base::set_ops(&shared_target_type<SharedFunction>::ops);
}
copy_shared_target_to(copy);
}
}
// Used by derived "impl" classes to implement operator()().
// Invokes the function's target.
// Note that fit::callback will release the target immediately after
// invoke() (also affecting any share()d copies).
// Aborts if the function's target is empty.
// TODO(b/241567321): Remove "no sanitize" after pw_protobuf is fixed.
Result invoke(Args... args) const PW_NO_SANITIZE("function") {
// Down cast the ops to the derived type that this function was instantiated
// with, which includes the invoke function.
//
// NOTE: This abuses the calling convention when invoking a null function
// that takes arguments! Null functions share a single vtable with a void()
// invoke function. This is permitted only because invoking a null function
// is an error that immediately aborts execution. Also, the null invoke
// function never attempts to access any passed arguments.
return static_cast<ops_type>(base::ops())->invoke(base::bits(), std::forward<Args>(args)...);
}
// Used by derived "impl" classes to implement operator=().
// Assigns the function's target.
// If target == nullptr, assigns an empty target.
template <typename Callable,
typename = std::enable_if_t<std::is_convertible<
decltype(std::declval<Callable&>()(std::declval<Args>()...)), result_type>::value>>
void assign_callable(Callable&& target) {
base::destroy_target();
initialize_target(std::forward<Callable>(target));
}
private:
// fit::function and fit::callback are not directly copyable, but share()
// will create shared references to the original object. This method
// implements the copy operation for the |std::shared_ptr| wrapper.
template <typename SharedFunction>
void copy_shared_target_to(SharedFunction& copy) {
copy.destroy_target();
PW_ASSERT(base::ops() == &shared_target_type<SharedFunction>::ops);
shared_target_type<SharedFunction>::copy_shared_ptr(base::bits(), copy.bits());
copy.set_ops(base::ops());
}
// target may or may not be initialized.
template <typename Callable>
void initialize_target(Callable&& target) {
// Convert function or function references to function pointer.
using DecayedCallable = std::decay_t<Callable>;
static_assert(!require_inline || alignof(DecayedCallable) <= alignof(base),
"Alignment of Callable must be <= alignment of the function class.");
static_assert(!require_inline || sizeof(DecayedCallable) <= inline_target_size,
"Callable too large to store inline as requested.");
if (is_null(target)) {
base::initialize_null_target();
} else {
base::set_ops(&target_type<DecayedCallable>::ops);
target_type<DecayedCallable>::initialize(base::bits(), std::forward<Callable>(target));
}
}
// Called by target() if |check| is true.
// Checks the template parameter, usually inferred from the context of
// the call to target(), and aborts the program if it can determine that
// the Callable type is not compatible with the function's Result and Args.
template <typename Callable>
void check_target_type() const {
if (target_type<Callable>::ops.target_type_id(nullptr, &target_type<Callable>::ops) !=
base::target_type_id()) {
PW_ASSERT(false);
}
}
};
} // namespace internal
} // namespace fit
#endif // LIB_FIT_INTERNAL_FUNCTION_H_