diff --git a/pw_function/BUILD.bazel b/pw_function/BUILD.bazel index 273ae58ba..de6d2e6d8 100644 --- a/pw_function/BUILD.bazel +++ b/pw_function/BUILD.bazel @@ -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", ], ) diff --git a/pw_function/BUILD.gn b/pw_function/BUILD.gn index 2302fa23f..f6b08819a 100644 --- a/pw_function/BUILD.gn +++ b/pw_function/BUILD.gn @@ -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") { diff --git a/pw_function/CMakeLists.txt b/pw_function/CMakeLists.txt index c0b891b33..fa35511ec 100644 --- a/pw_function/CMakeLists.txt +++ b/pw_function/CMakeLists.txt @@ -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) diff --git a/pw_function/docs.rst b/pw_function/docs.rst index d791547d1..a93d254fb 100644 --- a/pw_function/docs.rst +++ b/pw_function/docs.rst @@ -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 @@ -105,26 +105,10 @@ is a compile-time error. // Compiler error: sizeof(MyCallable) exceeds function's inline storage size. pw::Function 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 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 ========= @@ -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 `_ -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 `_ +. Zephyr ====== diff --git a/pw_function/function_test.cc b/pw_function/function_test.cc index 1c1036d7d..2f25cfc7d 100644 --- a/pw_function/function_test.cc +++ b/pw_function/function_test.cc @@ -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 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 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 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 function_pointer; @@ -236,9 +236,9 @@ class MoveTracker { TEST(Function, Move_CustomObject) { Function moved((MoveTracker())); - EXPECT_EQ(moved(), 2); // internally moves twice on construction + EXPECT_EQ(moved(), 1); Function tracker(std::move(moved)); - EXPECT_EQ(tracker(), 3); + EXPECT_EQ(tracker(), 2); // Ignore use-after-move. #ifndef __clang_analyzer__ @@ -248,9 +248,9 @@ TEST(Function, Move_CustomObject) { TEST(Function, MoveAssign_CustomObject) { Function moved((MoveTracker())); - EXPECT_EQ(moved(), 2); // internally moves twice on construction + EXPECT_EQ(moved(), 1); Function tracker = std::move(moved); - EXPECT_EQ(tracker(), 3); + EXPECT_EQ(tracker(), 2); // Ignore use-after-move. #ifndef __clang_analyzer__ diff --git a/pw_function/public/pw_function/config.h b/pw_function/public/pw_function/config.h index b98db4b87..b83489adc 100644 --- a/pw_function/public/pw_function/config.h +++ b/pw_function/public/pw_function/config.h @@ -21,10 +21,10 @@ // 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 && @@ -32,8 +32,6 @@ static_assert(PW_FUNCTION_INLINE_CALLABLE_SIZE > 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 diff --git a/pw_function/public/pw_function/function.h b/pw_function/public/pw_function/function.h index c0bc2c539..02037cd32 100644 --- a/pw_function/public/pw_function/function.h +++ b/pw_function/public/pw_function/function.h @@ -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: @@ -33,7 +34,7 @@ namespace pw { // return true; // } // -// bool ElementsArePostive(const pw::Vector& items) { +// bool ElementsArePositive(const pw::Vector& items) { // return All(items, [](const int& i) { return i > 0; }); // } // @@ -43,119 +44,15 @@ namespace pw { // return All(items, IsEven); // } // -template -class Function { - static_assert(std::is_function_v, - "pw::Function may only be instantiated for a function type, " - "such as pw::Function."); -}; -using Closure = Function; - -template -class Function { - public: - constexpr Function() = default; - constexpr Function(std::nullptr_t) : Function() {} - - Function(const Function&) = delete; - Function& operator=(const Function&) = delete; - - template - Function(Callable&& callable) { - if (function_internal::IsNull(callable)) { - holder_.InitializeNullTarget(); - } else { - holder_.InitializeInlineTarget(std::forward(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 - Function& operator=(Callable&& callable) { - holder_.DestructTarget(); - if (function_internal::IsNull(callable)) { - holder_.InitializeNullTarget(); - } else { - holder_.InitializeInlineTarget(std::forward(callable)); - } - return *this; - } - - ~Function() { holder_.DestructTarget(); } +template +using Function = fit::function_impl< + inline_target_size, + /*require_inline=*/!function_internal::config::kEnableDynamicAllocation, + Callable>; - template - Return operator()(PassedArgs&&... args) const { - return holder_.target()(std::forward(args)...); - } - - explicit operator bool() const { return !holder_.target().IsNull(); } - - private: - // TODO(frolv): This is temporarily private while the API is worked out. - template - Function(Callable&& callable, - function_internal::FunctionStorage& 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 - 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 -bool operator==(const Function& f, std::nullptr_t) { - return !static_cast(f); -} - -template -bool operator!=(const Function& f, std::nullptr_t) { - return static_cast(f); -} - -template -bool operator==(std::nullptr_t, const Function& f) { - return !static_cast(f); -} - -template -bool operator!=(std::nullptr_t, const Function& f) { - return static_cast(f); -} +using Closure = Function; } // namespace pw diff --git a/pw_function/public/pw_function/internal/function.h b/pw_function/public/pw_function/internal/function.h deleted file mode 100644 index 381cebb16..000000000 --- a/pw_function/public/pw_function/internal/function.h +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2021 The Pigweed Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -#pragma once - -#include -#include -#include - -#include "pw_assert/assert.h" -#include "pw_function/config.h" -#include "pw_preprocessor/compiler.h" - -namespace pw::function_internal { - -template -struct NullEq { - static constexpr bool Test(const T&) { return false; } -}; - -// Partial specialization for values of T comparable to nullptr. -template -struct NullEq() == nullptr)> { - // This is intended to be used for comparing function pointers to nullptr, but - // the specialization also matches Ts that implicitly convert to a function - // pointer, such as function types. The compiler may then complain that the - // comparison is false, as the address is known at compile time and cannot be - // nullptr. Silence this warning. (The compiler will optimize out the - // comparison.) - PW_MODIFY_DIAGNOSTICS_PUSH(); - PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Waddress"); - PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wnonnull-compare"); - static constexpr bool Test(const T& v) { return v == nullptr; } - PW_MODIFY_DIAGNOSTICS_POP(); -}; - -// Tests whether a value is considered to be null. -template -static constexpr bool IsNull(const T& v) { - return NullEq::Test(v); -} - -// FunctionTarget is an interface for storing a callable object and providing a -// way to invoke it. The GenericFunctionTarget expresses the interface common to -// all pw::Function instantiations. The derived FunctionTarget class adds the -// call operator, which is templated on the function arguments and return type. -class GenericFunctionTarget { - public: - constexpr GenericFunctionTarget() = default; - - GenericFunctionTarget(const GenericFunctionTarget&) = delete; - GenericFunctionTarget(GenericFunctionTarget&&) = delete; - GenericFunctionTarget& operator=(const GenericFunctionTarget&) = delete; - GenericFunctionTarget& operator=(GenericFunctionTarget&&) = delete; - - virtual void Destroy() {} - - // Only returns true for NullFunctionTarget. - virtual bool IsNull() const { return false; } - - // Move initialize the function target to a provided location. - virtual void MoveInitializeTo(void* ptr) = 0; - - protected: - ~GenericFunctionTarget() = default; // The destructor is never called. -}; - -// FunctionTarget is an interface for storing a callable object and providing a -// way to invoke it. -template -class FunctionTarget : public GenericFunctionTarget { - public: - constexpr FunctionTarget() = default; - - // Invoke the callable stored by the function target. - virtual Return operator()(Args... args) const = 0; - - protected: - ~FunctionTarget() = default; // The destructor is never called. -}; - -// A function target that does not store any callable. Attempting to invoke it -// results in a crash. -class NullFunctionTarget final : public FunctionTarget { - public: - constexpr NullFunctionTarget() = default; - - NullFunctionTarget(const NullFunctionTarget&) = delete; - NullFunctionTarget(NullFunctionTarget&&) = delete; - NullFunctionTarget& operator=(const NullFunctionTarget&) = delete; - NullFunctionTarget& operator=(NullFunctionTarget&&) = delete; - - bool IsNull() const final { return true; } - - void operator()() const final { PW_ASSERT(false); } - - void MoveInitializeTo(void* ptr) final { new (ptr) NullFunctionTarget(); } -}; - -// Function target that stores a callable as a member within the class. -template -class InlineFunctionTarget final : public FunctionTarget { - public: - explicit InlineFunctionTarget(Callable&& callable) - : callable_(std::move(callable)) {} - - void Destroy() final { callable_.~Callable(); } - - InlineFunctionTarget(const InlineFunctionTarget&) = delete; - InlineFunctionTarget& operator=(const InlineFunctionTarget&) = delete; - - InlineFunctionTarget(InlineFunctionTarget&& other) - : callable_(std::move(other.callable_)) {} - InlineFunctionTarget& operator=(InlineFunctionTarget&&) = default; - - Return operator()(Args... args) const final { - return callable_(std::forward(args)...); - } - - void MoveInitializeTo(void* ptr) final { - new (ptr) InlineFunctionTarget(std::move(*this)); - } - - private: - // This must be mutable to support custom objects that implement operator() in - // a non-const way. - mutable Callable callable_; -}; - -// Function target which stores a callable at a provided location in memory. -// The creating context must ensure that the region is properly sized and -// aligned for the callable. -template -class MemoryFunctionTarget final : public FunctionTarget { - public: - MemoryFunctionTarget(void* address, Callable&& callable) : address_(address) { - new (address_) Callable(std::move(callable)); - } - - void Destroy() final { - // Multiple MemoryFunctionTargets may have referred to the same callable - // (due to moves), but only one can have a valid pointer to it. The owner is - // responsible for destructing the callable. - if (address_ != nullptr) { - callable().~Callable(); - } - } - - MemoryFunctionTarget(const MemoryFunctionTarget&) = delete; - MemoryFunctionTarget& operator=(const MemoryFunctionTarget&) = delete; - - // Transfer the pointer to the initialized callable to this object without - // reinitializing the callable, clearing the address from the other. - MemoryFunctionTarget(MemoryFunctionTarget&& other) - : address_(other.address_) { - other.address_ = nullptr; - } - MemoryFunctionTarget& operator=(MemoryFunctionTarget&&) = default; - - Return operator()(Args... args) const final { return callable()(args...); } - - void MoveInitializeTo(void* ptr) final { - new (ptr) MemoryFunctionTarget(std::move(*this)); - } - - private: - Callable& callable() { - return *std::launder(reinterpret_cast(address_)); - } - const Callable& callable() const { - return *std::launder(reinterpret_cast(address_)); - } - - void* address_; -}; - -template -using FunctionStorage = - std::aligned_storage_t; - -// A FunctionTargetHolder stores an instance of a FunctionTarget implementation. -// -// The concrete implementation is initialized in an internal buffer by calling -// one of the initialization functions. After initialization, all -// implementations are accessed through the virtual FunctionTarget base. -template -class FunctionTargetHolder { - public: - constexpr FunctionTargetHolder() : null_function_ {} - {} - - FunctionTargetHolder(const FunctionTargetHolder&) = delete; - FunctionTargetHolder(FunctionTargetHolder&&) = delete; - FunctionTargetHolder& operator=(const FunctionTargetHolder&) = delete; - FunctionTargetHolder& operator=(FunctionTargetHolder&&) = delete; - - constexpr void InitializeNullTarget() { - static_assert(sizeof(NullFunctionTarget) <= kSizeBytes, - "NullFunctionTarget must fit within FunctionTargetHolder"); - new (&null_function_) NullFunctionTarget; - } - - // Initializes an InlineFunctionTarget with the callable, failing if it is too - // large. - template - void InitializeInlineTarget(Callable callable) { - using InlineFunctionTarget = - InlineFunctionTarget; - static_assert(sizeof(InlineFunctionTarget) <= kSizeBytes, - "Inline callable must fit within FunctionTargetHolder"); - new (&bits_) InlineFunctionTarget(std::move(callable)); - } - - // Initializes a MemoryTarget that stores the callable at the provided - // location. - template - void InitializeMemoryTarget(Callable callable, void* storage) { - using MemoryFunctionTarget = - MemoryFunctionTarget; - static_assert(sizeof(MemoryFunctionTarget) <= kSizeBytes, - "MemoryFunctionTarget must fit within FunctionTargetHolder"); - new (&bits_) MemoryFunctionTarget(storage, std::move(callable)); - } - - void DestructTarget() { target().Destroy(); } - - // Initializes the function target within this callable from another target - // holder's function target. - void MoveInitializeTargetFrom(FunctionTargetHolder& other) { - other.target().MoveInitializeTo(&bits_); - } - - // The stored implementation is accessed by punning to the virtual base class. - using Target = FunctionTarget; - Target& target() { return *std::launder(reinterpret_cast(&bits_)); } - const Target& target() const { - return *std::launder(reinterpret_cast(&bits_)); - } - - private: - // Storage for an implementation of the FunctionTarget interface. Make this a - // union with NullFunctionTarget so that the constexpr constructor can - // initialize null_function_ directly. - union { - FunctionStorage bits_; - NullFunctionTarget null_function_; - }; -}; - -} // namespace pw::function_internal