Skip to content

Commit

Permalink
pw_function: Replace implementation with alias to fit function
Browse files Browse the repository at this point in the history
Fixes: b/239061451
Change-Id: I2792b2a1417767a8dadf29c5ccb07468689bab01
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/103213
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Vadim Spivak <vadims@google.com>
  • Loading branch information
Vadim Spivak authored and CQ Bot Account committed Aug 8, 2022
1 parent fad4926 commit 85c4cfc
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 420 deletions.
2 changes: 1 addition & 1 deletion pw_function/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ pw_cc_library(

pw_cc_library(
name = "pw_function",
srcs = ["public/pw_function/internal/function.h"],
hdrs = ["public/pw_function/function.h"],
includes = ["public"],
deps = [
":config",
"//pw_assert",
"//pw_preprocessor",
"//third_party/fuchsia:fit",
],
)

Expand Down
2 changes: 1 addition & 1 deletion pw_function/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ pw_source_set("pw_function") {
public_configs = [ ":public_include_path" ]
public_deps = [
":config",
"$dir_pw_third_party/fuchsia:fit",
dir_pw_assert,
dir_pw_preprocessor,
]
public = [ "public/pw_function/function.h" ]
sources = [ "public/pw_function/internal/function.h" ]
}

pw_doc_group("docs") {
Expand Down
2 changes: 1 addition & 1 deletion pw_function/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ pw_add_module_library(pw_function.config
pw_add_module_library(pw_function
HEADERS
public/pw_function/function.h
public/pw_function/internal/function.h
PUBLIC_INCLUDES
public
PUBLIC_DEPS
pw_assert
pw_function.config
pw_preprocessor
pw_third_party.fuchsia.fit
)
if(Zephyr_FOUND AND CONFIG_PIGWEED_FUNCTION)
zephyr_link_libraries(pw_function)
Expand Down
36 changes: 7 additions & 29 deletions pw_function/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ through the build system. The size of a ``Function`` object is equivalent to its
inline storage size.

Attempting to construct a function from a callable larger than its inline size
is a compile-time error.
is a compile-time error unless dynamic allocation is enabled.

.. admonition:: Inline storage size

Expand All @@ -105,26 +105,10 @@ is a compile-time error.
// Compiler error: sizeof(MyCallable) exceeds function's inline storage size.
pw::Function<int(int)> function((MyCallable()));

..
For larger callables, a ``Function`` can be constructed with an external buffer
in which the callable should be stored. The user must ensure that the lifetime
of the buffer exceeds that of the function object.
.. admonition:: Dynamic allocation

.. code-block:: c++

// Initialize a function with an external 16-byte buffer in which to store its
// callable. The callable will be stored in the buffer regardless of whether
// it fits inline.
pw::FunctionStorage<16> storage;
pw::Function<int()> get_random_number([]() { return 4; }, storage);

.. admonition:: External storage

Functions which use external storage still take up the configured inline
storage size, which should be accounted for when storing function objects.

In the future, ``pw::Function`` may support dynamic allocation of callable
storage using the system allocator. This operation will always be explicit.
When ``PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION`` is enabled, a ``Function``
will use dynamic allocation to store callables that exceed the inline size.

API usage
=========
Expand Down Expand Up @@ -237,15 +221,9 @@ be used as a reference when sizing external buffers for ``Function`` objects.

Design
======
``pw::Function`` is based largely on
`fbl::Function <https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/ulib/fbl/include/fbl/function.h;drc=eb7f31a1264b2443e9d3108b359969253e251c07>`_
from Fuchsia with some changes to make it more suitable for embedded
development.

Functions are movable, but not copyable. This allows them to store and manage
callables without having to perform bookkeeping such as reference counting, and
avoids any reliance on dynamic memory management. The result is a simpler
implementation which is easy to conceptualize and use in an embedded context.
``pw::Function`` is an alias of
`fit::function <https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/fit/include/lib/fit/function.h;drc=f66f54fca0c11a1168d790bcc3d8a5a3d940218d>`_
.

Zephyr
======
Expand Down
16 changes: 8 additions & 8 deletions pw_function/function_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ namespace pw {
namespace {

#if PW_NC_TEST(CannotInstantiateWithNonFunction)
PW_NC_EXPECT("pw::Function may only be instantiated for a function type");
PW_NC_EXPECT("incomplete type|undefined template");

[[maybe_unused]] Function<int> function_pointer;

#elif PW_NC_TEST(CannotInstantiateWithFunctionPointer1)
PW_NC_EXPECT("pw::Function may only be instantiated for a function type");
PW_NC_EXPECT("incomplete type|undefined template");

[[maybe_unused]] Function<void (*)()> function_pointer;

#elif PW_NC_TEST(CannotInstantiateWithFunctionPointer2)
PW_NC_EXPECT("pw::Function may only be instantiated for a function type");
PW_NC_EXPECT("incomplete type|undefined template");

[[maybe_unused]] void SomeFunction(int);

[[maybe_unused]] Function<decltype(&SomeFunction)> function_pointer;

#elif PW_NC_TEST(CannotInstantiateWithFunctionReference)
PW_NC_EXPECT("pw::Function may only be instantiated for a function type");
PW_NC_EXPECT("incomplete type|undefined template");

[[maybe_unused]] Function<void (&)()> function_pointer;

Expand Down Expand Up @@ -236,9 +236,9 @@ class MoveTracker {

TEST(Function, Move_CustomObject) {
Function<int()> moved((MoveTracker()));
EXPECT_EQ(moved(), 2); // internally moves twice on construction
EXPECT_EQ(moved(), 1);
Function<int()> tracker(std::move(moved));
EXPECT_EQ(tracker(), 3);
EXPECT_EQ(tracker(), 2);

// Ignore use-after-move.
#ifndef __clang_analyzer__
Expand All @@ -248,9 +248,9 @@ TEST(Function, Move_CustomObject) {

TEST(Function, MoveAssign_CustomObject) {
Function<int()> moved((MoveTracker()));
EXPECT_EQ(moved(), 2); // internally moves twice on construction
EXPECT_EQ(moved(), 1);
Function<int()> tracker = std::move(moved);
EXPECT_EQ(tracker(), 3);
EXPECT_EQ(tracker(), 2);

// Ignore use-after-move.
#ifndef __clang_analyzer__
Expand Down
8 changes: 3 additions & 5 deletions pw_function/public/pw_function/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,17 @@
// also the size of the Function object itself. Callables larger than this are
// stored externally to the function.
//
// This defaults to 2 pointers, which is capable of storing common callables
// such as function pointers and simple lambdas.
// This defaults to 1 pointer, which is capable of storing common callables
// such as function pointers and lambdas with a single capture.
#ifndef PW_FUNCTION_INLINE_CALLABLE_SIZE
#define PW_FUNCTION_INLINE_CALLABLE_SIZE (2 * sizeof(void*))
#define PW_FUNCTION_INLINE_CALLABLE_SIZE (sizeof(void*))
#endif // PW_FUNCTION_INLINE_CALLABLE_SIZE

static_assert(PW_FUNCTION_INLINE_CALLABLE_SIZE > 0 &&
PW_FUNCTION_INLINE_CALLABLE_SIZE % alignof(void*) == 0);

// Whether functions should allocate memory dynamically (using operator new) if
// a callable is larger than the inline size.
//
// NOTE: This is not currently used.
#ifndef PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION
#define PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION 0
#endif // PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION
Expand Down
127 changes: 12 additions & 115 deletions pw_function/public/pw_function/function.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
// the License.
#pragma once

#include "pw_function/internal/function.h"
#include "lib/fit/function.h"
#include "pw_function/config.h"

namespace pw {

// pw::Function is a wrapper for an aribtrary callable object. It can be used by
// pw::Function is a wrapper for an arbitrary callable object. It can be used by
// callback-based APIs to allow callers to provide any type of callable.
//
// Example:
Expand All @@ -33,7 +34,7 @@ namespace pw {
// return true;
// }
//
// bool ElementsArePostive(const pw::Vector<int>& items) {
// bool ElementsArePositive(const pw::Vector<int>& items) {
// return All(items, [](const int& i) { return i > 0; });
// }
//
Expand All @@ -43,119 +44,15 @@ namespace pw {
// return All(items, IsEven);
// }
//
template <typename Callable>
class Function {
static_assert(std::is_function_v<Callable>,
"pw::Function may only be instantiated for a function type, "
"such as pw::Function<void(int)>.");
};

using Closure = Function<void()>;

template <typename Return, typename... Args>
class Function<Return(Args...)> {
public:
constexpr Function() = default;
constexpr Function(std::nullptr_t) : Function() {}

Function(const Function&) = delete;
Function& operator=(const Function&) = delete;

template <typename Callable>
Function(Callable&& callable) {
if (function_internal::IsNull(callable)) {
holder_.InitializeNullTarget();
} else {
holder_.InitializeInlineTarget(std::forward<Callable>(callable));
}
}

Function(Function&& other) {
holder_.MoveInitializeTargetFrom(other.holder_);
other.holder_.InitializeNullTarget();
}

Function& operator=(Function&& other) {
holder_.DestructTarget();
holder_.MoveInitializeTargetFrom(other.holder_);
other.holder_.InitializeNullTarget();
return *this;
}

Function& operator=(std::nullptr_t) {
holder_.DestructTarget();
holder_.InitializeNullTarget();
return *this;
}

template <typename Callable>
Function& operator=(Callable&& callable) {
holder_.DestructTarget();
if (function_internal::IsNull(callable)) {
holder_.InitializeNullTarget();
} else {
holder_.InitializeInlineTarget(std::forward<Callable>(callable));
}
return *this;
}

~Function() { holder_.DestructTarget(); }
template <typename Callable,
size_t inline_target_size =
function_internal::config::kInlineCallableSize>
using Function = fit::function_impl<
inline_target_size,
/*require_inline=*/!function_internal::config::kEnableDynamicAllocation,
Callable>;

template <typename... PassedArgs>
Return operator()(PassedArgs&&... args) const {
return holder_.target()(std::forward<PassedArgs>(args)...);
}

explicit operator bool() const { return !holder_.target().IsNull(); }

private:
// TODO(frolv): This is temporarily private while the API is worked out.
template <typename Callable, size_t kSizeBytes>
Function(Callable&& callable,
function_internal::FunctionStorage<kSizeBytes>& storage)
: Function(callable, &storage) {
static_assert(sizeof(Callable) <= kSizeBytes,
"pw::Function callable does not fit into provided storage");
}

// Constructs a function that stores its callable at the provided location.
// Public constructors wrapping this must ensure that the memory region is
// capable of storing the callable in terms of both size and alignment.
template <typename Callable>
Function(Callable&& callable, void* storage) {
if (function_internal::IsNull(callable)) {
holder_.InitializeNullTarget();
} else {
holder_.InitializeMemoryTarget(std::forward(callable), storage);
}
}

function_internal::FunctionTargetHolder<
function_internal::config::kInlineCallableSize,
Return,
Args...>
holder_;
};

// nullptr comparisions for functions.
template <typename T>
bool operator==(const Function<T>& f, std::nullptr_t) {
return !static_cast<bool>(f);
}

template <typename T>
bool operator!=(const Function<T>& f, std::nullptr_t) {
return static_cast<bool>(f);
}

template <typename T>
bool operator==(std::nullptr_t, const Function<T>& f) {
return !static_cast<bool>(f);
}

template <typename T>
bool operator!=(std::nullptr_t, const Function<T>& f) {
return static_cast<bool>(f);
}
using Closure = Function<void()>;

} // namespace pw
Loading

0 comments on commit 85c4cfc

Please sign in to comment.