pw_function: Standard callback API This implements pw::Function, a standard wrapper for callable objects. This CL is currently provided as a proof-of-concept implementation based on fbl::Function, and does not necessarily represent the final design of pw::Function. Change-Id: Ie2f5714b9711a08de878471e8f31fc46b26d36e8 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/46080 Reviewed-by: Keir Mierle <keir@google.com> Reviewed-by: Wyatt Hepler <hepler@google.com> Commit-Queue: Alexei Frolov <frolv@google.com>
diff --git a/BUILD.gn b/BUILD.gn index 724529c..7b482ff 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -257,6 +257,7 @@ "$dir_pw_chrono:tests", "$dir_pw_containers:tests", "$dir_pw_cpu_exception_cortex_m:tests", + "$dir_pw_function:tests", "$dir_pw_fuzzer:tests", "$dir_pw_hdlc:tests", "$dir_pw_hex_dump:tests",
diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b707ca..1c7b277 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -37,6 +37,7 @@ add_subdirectory(pw_containers EXCLUDE_FROM_ALL) add_subdirectory(pw_cpu_exception EXCLUDE_FROM_ALL) add_subdirectory(pw_cpu_exception_cortex_m EXCLUDE_FROM_ALL) +add_subdirectory(pw_function EXCLUDE_FROM_ALL) add_subdirectory(pw_hdlc EXCLUDE_FROM_ALL) add_subdirectory(pw_kvs EXCLUDE_FROM_ALL) add_subdirectory(pw_log EXCLUDE_FROM_ALL)
diff --git a/docs/BUILD.gn b/docs/BUILD.gn index 428ba25..7b0b536 100644 --- a/docs/BUILD.gn +++ b/docs/BUILD.gn
@@ -81,6 +81,7 @@ "$dir_pw_docgen:docs", "$dir_pw_doctor:docs", "$dir_pw_env_setup:docs", + "$dir_pw_function:docs", "$dir_pw_fuzzer:docs", "$dir_pw_hdlc:docs", "$dir_pw_hex_dump:docs",
diff --git a/modules.gni b/modules.gni index b8ac64f..cc01d95 100644 --- a/modules.gni +++ b/modules.gni
@@ -1,4 +1,4 @@ -# Copyright 2020 The Pigweed Authors +# 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 @@ -42,8 +42,10 @@ get_path_info("pw_cpu_exception_cortex_m", "abspath") dir_pw_docgen = get_path_info("pw_docgen", "abspath") dir_pw_doctor = get_path_info("pw_doctor", "abspath") - dir_pw_hex_dump = get_path_info("pw_hex_dump", "abspath") dir_pw_env_setup = get_path_info("pw_env_setup", "abspath") + dir_pw_function = get_path_info("pw_function", "abspath") + dir_pw_fuzzer = get_path_info("pw_fuzzer", "abspath") + dir_pw_hex_dump = get_path_info("pw_hex_dump", "abspath") dir_pw_hdlc = get_path_info("pw_hdlc", "abspath") dir_pw_i2c = get_path_info("pw_i2c", "abspath") dir_pw_interrupt = get_path_info("pw_interrupt", "abspath") @@ -63,7 +65,6 @@ dir_pw_minimal_cpp_stdlib = get_path_info("pw_minimal_cpp_stdlib", "abspath") dir_pw_module = get_path_info("pw_module", "abspath") dir_pw_multisink = get_path_info("pw_multisink", "abspath") - dir_pw_fuzzer = get_path_info("pw_fuzzer", "abspath") dir_pw_package = get_path_info("pw_package", "abspath") dir_pw_persistent_ram = get_path_info("pw_persistent_ram", "abspath") dir_pw_polyfill = get_path_info("pw_polyfill", "abspath")
diff --git a/pw_function/BUILD b/pw_function/BUILD new file mode 100644 index 0000000..768cc3c --- /dev/null +++ b/pw_function/BUILD
@@ -0,0 +1,47 @@ +# 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. + +load( + "//pw_build:pigweed.bzl", + "pw_cc_library", + "pw_cc_test", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache License 2.0 + +pw_cc_library( + name = "config", + hdrs = ["public/pw_function/config.h"], + includes = ["public"], +) + +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", + ], +) + +pw_cc_test( + name = "function_test", + srcs = ["function_test.cc"], + deps = [":pw_function"], +)
diff --git a/pw_function/BUILD.gn b/pw_function/BUILD.gn new file mode 100644 index 0000000..82009b1 --- /dev/null +++ b/pw_function/BUILD.gn
@@ -0,0 +1,117 @@ +# 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. + +import("//build_overrides/pigweed.gni") + +import("$dir_pw_bloat/bloat.gni") +import("$dir_pw_build/module_config.gni") +import("$dir_pw_build/target_types.gni") +import("$dir_pw_docgen/docs.gni") +import("$dir_pw_unit_test/test.gni") + +declare_args() { + # The build target that overrides the default configuration options for this + # module. This should point to a source set that provides defines through a + # public config (which may -include a file or add defines directly). + pw_function_CONFIG = pw_build_DEFAULT_MODULE_CONFIG +} + +config("public_include_path") { + include_dirs = [ "public" ] + visibility = [ ":*" ] +} + +pw_source_set("config") { + public = [ "public/pw_function/config.h" ] + public_configs = [ ":public_include_path" ] + public_deps = [ pw_function_CONFIG ] + visibility = [ ":*" ] +} + +pw_source_set("pw_function") { + public_configs = [ ":public_include_path" ] + public_deps = [ + ":config", + dir_pw_assert, + dir_pw_preprocessor, + ] + public = [ "public/pw_function/function.h" ] + sources = [ "public/pw_function/internal/function.h" ] +} + +pw_doc_group("docs") { + sources = [ "docs.rst" ] + report_deps = [ + ":callable_size", + ":function_size", + ] +} + +pw_test_group("tests") { + tests = [ ":function_test" ] +} + +pw_test("function_test") { + deps = [ ":pw_function" ] + sources = [ "function_test.cc" ] +} + +pw_size_report("function_size") { + title = "Pigweed function size report" + + binaries = [ + { + target = "size_report:basic_function" + base = "size_report:pointer_base" + label = "Simple pw::Function vs. function pointer" + }, + ] +} + +pw_size_report("callable_size") { + title = "Size comparison of callable objects" + + binaries = [ + { + target = "size_report:callable_size_function_pointer" + base = "size_report:callable_size_base" + label = "Function pointer" + }, + { + target = "size_report:callable_size_static_lambda" + base = "size_report:callable_size_base" + label = "Static lambda (operator+)" + }, + { + target = "size_report:callable_size_simple_lambda" + base = "size_report:callable_size_base" + label = "Non-capturing lambda" + }, + { + target = "size_report:callable_size_capturing_lambda" + base = "size_report:callable_size_base" + label = "Simple capturing lambda" + }, + { + target = "size_report:callable_size_multi_capturing_lambda" + base = "size_report:callable_size_base" + label = "Multi-argument capturing lambda" + }, + { + target = "size_report:callable_size_custom_class" + base = "size_report:callable_size_base" + label = "Custom class" + }, + ] +}
diff --git a/pw_function/CMakeLists.txt b/pw_function/CMakeLists.txt new file mode 100644 index 0000000..6f6e275 --- /dev/null +++ b/pw_function/CMakeLists.txt
@@ -0,0 +1,21 @@ +# 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. + +include($ENV{PW_ROOT}/pw_build/pigweed.cmake) + +pw_auto_add_simple_module(pw_function + PUBLIC_DEPS + pw_assert + pw_preprocessor +)
diff --git a/pw_function/docs.rst b/pw_function/docs.rst new file mode 100644 index 0000000..fa6e68e --- /dev/null +++ b/pw_function/docs.rst
@@ -0,0 +1,178 @@ +.. _module-pw_function: + +----------- +pw_function +----------- +The function module provides a standard, general-purpose API for wrapping +callable objects. + +.. note:: + This module is under construction and its API is not complete. + +Overview +======== + +Basic usage +----------- +``pw_function`` defines the ``pw::Function`` class. A ``Function`` is a +move-only callable wrapper constructable from any callable object. Functions +are templated on the signature of the callable they store. + +Functions implement the call operator --- invoking the object will forward to +the stored callable. + +.. code-block:: c++ + + int Add(int a, int b) { return a + b; } + + // Construct a Function object from a function pointer. + pw::Function<int(int, int)> add_function(Add); + + // Invoke the function object. + int result = add_function(3, 5); + EXPECT_EQ(result, 8); + + // Construct a function from a lambda. + pw::Function<int(int)> negate([](int value) { return -value; }); + EXPECT_EQ(negate(27), -27); + +Functions are nullable. Invoking a null function triggers a runtime assert. + +.. code-block:: c++ + + // A function intialized without a callable is implicitly null. + pw::Function<void()> null_function; + + // Null functions may also be explicitly created or set. + pw::Function<void()> explicit_null_function(nullptr); + + pw::Function<void()> function([]() {}); // Valid (non-null) function. + function = nullptr; // Set to null, clearing the stored callable. + + // Functions are comparable to nullptr. + if (function != nullptr) { + function(); + } + +Storage +------- +By default, a ``Function`` stores its callable inline within the object. The +inline storage size defaults to the size of two pointers, but is configurable +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. + +.. admonition:: Inline storage size + + The default inline size of two pointers is sufficient to store most common + callable objects, including function pointers, simple non-capturing and + capturing lambdas, and lightweight custom classes. + +.. code-block:: c++ + + // The lambda is moved into the function's internal storage. + pw::Function<int(int, int)> subtract([](int a, int b) { return a - b; }); + + // Functions can be also be constructed from custom classes that implement + // operator(). This particular object is large (8 ints of space). + class MyCallable { + public: + int operator()(int value); + + private: + int data_[8]; + }; + + // 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. + + .. 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. + +API usage +========= + +Implementation-side +------------------- +When implementing an API which takes a callback, a ``Function`` can be used in +place of a function pointer or equivalent callable. + +.. code-block:: c++ + + // Before: + void DoTheThing(int arg, void (*callback)(int result)); + + // After. Note that it is possible to have parameter names within the function + // signature template for clarity. + void DoTheThing(int arg, pw::Function<void(int result)> callback); + +An API can accept a function either by value or by reference. If taken by value, +the implementation is responsible for managing the function by moving it into an +appropriate location. + +.. admonition:: Value or reference? + + It is preferable for APIs to take functions by value rather than by reference. + This provides callers of the API with a more convenient interface, as well as + making their lives easier by not requiring management of resources or + lifetimes. + +Caller-side +----------- +When calling an API which takes a function by reference, the standard pattern is +to implicitly construct the function in place from a callable object. Simply +pass the desired callable directly to the API. + +.. code-block:: c++ + + // Implicitly initialize a Function from a capturing lambda. + DoTheThing(42, [this](int result) { result_ = result; }); + +Size reports +============ + +Function class +-------------- +The following size report compares an API using a ``pw::Function`` to a +traditional function pointer. + +.. include:: function_size + +Callable sizes +-------------- +The table below demonstrates typical sizes of various callable types, which can +be used as a reference when sizing external buffers for ``Function`` objects. + +.. include:: callable_size + +Design +====== +``pw::Function`` is based largely on +`fbl::Function <https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/ulib/fbl/include/fbl/function.h>`_ +from Fuchsia with some changes to make it more suitable for embedded +development. + +Functions are moveable, 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.
diff --git a/pw_function/function_test.cc b/pw_function/function_test.cc new file mode 100644 index 0000000..f76181c --- /dev/null +++ b/pw_function/function_test.cc
@@ -0,0 +1,226 @@ +// 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. + +#include "pw_function/function.h" + +#include "gtest/gtest.h" + +namespace pw { +namespace { + +int Multiply(int a, int b) { return a * b; } + +TEST(Function, OperatorCall) { + Function<int(int, int)> multiply(Multiply); + EXPECT_EQ(multiply(3, 7), 21); +} + +void CallbackAdd(int a, int b, pw::Function<void(int sum)> callback) { + callback(a + b); +} + +int add_result = -1; + +void free_add_callback(int sum) { add_result = sum; } + +TEST(Function, ConstructInPlace_FreeFunction) { + add_result = -1; + CallbackAdd(25, 17, free_add_callback); + EXPECT_EQ(add_result, 42); +} + +TEST(Function, ConstructInPlace_NonCapturingLambda) { + add_result = -1; + CallbackAdd(25, 18, [](int sum) { add_result = sum; }); + EXPECT_EQ(add_result, 43); +} + +TEST(Function, ConstructInPlace_CapturingLambda) { + int result = -1; + CallbackAdd(25, 19, [&](int sum) { result = sum; }); + EXPECT_EQ(result, 44); +} + +class CallableObject { + public: + CallableObject(int* result) : result_(result) {} + + CallableObject(CallableObject&& other) = default; + CallableObject& operator=(CallableObject&& other) = default; + + void operator()(int sum) { *result_ = sum; } + + private: + int* result_; +}; + +TEST(Function, ConstructInPlace_CallableObject) { + int result = -1; + CallbackAdd(25, 20, CallableObject(&result)); + EXPECT_EQ(result, 45); +} + +class MemberFunctionTest : public ::testing::Test { + protected: + MemberFunctionTest() : result_(-1) {} + + void set_result(int result) { result_ = result; } + + int result_; +}; + +TEST_F(MemberFunctionTest, ConstructInPlace_Lambda) { + CallbackAdd(25, 21, [this](int sum) { set_result(sum); }); + EXPECT_EQ(result_, 46); +} + +TEST(Function, Null_OperatorBool) { + Closure implicit_null; + Closure explicit_null(nullptr); + Closure assigned_null = nullptr; + Closure not_null([]() {}); + + EXPECT_FALSE(bool(implicit_null)); + EXPECT_FALSE(bool(explicit_null)); + EXPECT_FALSE(bool(assigned_null)); + EXPECT_TRUE(bool(not_null)); + + EXPECT_TRUE(!implicit_null); + EXPECT_TRUE(!explicit_null); + EXPECT_TRUE(!assigned_null); + EXPECT_FALSE(!not_null); +} + +TEST(Function, Null_OperatorEquals) { + Closure implicit_null; + Closure explicit_null(nullptr); + Closure assigned_null = nullptr; + Closure not_null([]() {}); + + EXPECT_TRUE(implicit_null == nullptr); + EXPECT_TRUE(explicit_null == nullptr); + EXPECT_TRUE(assigned_null == nullptr); + EXPECT_TRUE(not_null != nullptr); + + EXPECT_FALSE(implicit_null != nullptr); + EXPECT_FALSE(explicit_null != nullptr); + EXPECT_FALSE(assigned_null != nullptr); + EXPECT_FALSE(not_null == nullptr); +} + +TEST(Function, Null_Set) { + Closure function = []() {}; + EXPECT_NE(function, nullptr); + function = nullptr; + EXPECT_EQ(function, nullptr); +} + +void DoNothing() {} + +TEST(Function, Null_FunctionPointer) { + void (*ptr)() = DoNothing; + Closure not_null(ptr); + EXPECT_NE(not_null, nullptr); + ptr = nullptr; + Closure is_null(ptr); + EXPECT_EQ(is_null, nullptr); +} + +TEST(Function, Move_Null) { + Closure moved; + EXPECT_EQ(moved, nullptr); + Closure function(std::move(moved)); + EXPECT_EQ(function, nullptr); + +// Ignore use-after-move. +#ifndef __clang_analyzer__ + EXPECT_EQ(moved, nullptr); +#endif // __clang_analyzer__ +} + +TEST(Function, MoveAssign_Null) { + Closure moved; + EXPECT_EQ(moved, nullptr); + Closure function = std::move(moved); + EXPECT_EQ(function, nullptr); + +// Ignore use-after-move. +#ifndef __clang_analyzer__ + EXPECT_EQ(moved, nullptr); +#endif // __clang_analyzer__ +} + +TEST(Function, Move_Inline) { + Function<int(int, int)> moved(Multiply); + EXPECT_NE(moved, nullptr); + Function<int(int, int)> multiply(std::move(moved)); + EXPECT_EQ(multiply(3, 3), 9); + +// Ignore use-after-move. +#ifndef __clang_analyzer__ + EXPECT_EQ(moved, nullptr); +#endif // __clang_analyzer__ +} + +TEST(Function, MoveAssign_Inline) { + Function<int(int, int)> moved(Multiply); + EXPECT_NE(moved, nullptr); + Function<int(int, int)> multiply = std::move(moved); + EXPECT_EQ(multiply(3, 3), 9); + +// Ignore use-after-move. +#ifndef __clang_analyzer__ + EXPECT_EQ(moved, nullptr); +#endif // __clang_analyzer__ +} + +class MoveTracker { + public: + MoveTracker() : move_count_(0) {} + + MoveTracker(MoveTracker&& other) : move_count_(other.move_count_ + 1) {} + MoveTracker& operator=(MoveTracker&& other) = default; + + int operator()() const { return move_count_; } + + private: + int move_count_; +}; + +TEST(Function, Move_CustomObject) { + Function<int()> moved((MoveTracker())); + EXPECT_EQ(moved(), 2); // internally moves twice on construction + Function<int()> tracker(std::move(moved)); + EXPECT_EQ(tracker(), 3); + +// Ignore use-after-move. +#ifndef __clang_analyzer__ + EXPECT_EQ(moved, nullptr); +#endif // __clang_analyzer__ +} + +TEST(Function, MoveAssign_CustomObject) { + Function<int()> moved((MoveTracker())); + EXPECT_EQ(moved(), 2); // internally moves twice on construction + Function<int()> tracker = std::move(moved); + EXPECT_EQ(tracker(), 3); + +// Ignore use-after-move. +#ifndef __clang_analyzer__ + EXPECT_EQ(moved, nullptr); +#endif // __clang_analyzer__ +} + +} // namespace +} // namespace pw
diff --git a/pw_function/public/pw_function/config.h b/pw_function/public/pw_function/config.h new file mode 100644 index 0000000..b98db4b --- /dev/null +++ b/pw_function/public/pw_function/config.h
@@ -0,0 +1,47 @@ +// 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. + +// Configuration macros for the function module. +#pragma once + +#include <cstddef> + +// The maximum size of a callable that can be inlined within a function. This is +// 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. +#ifndef PW_FUNCTION_INLINE_CALLABLE_SIZE +#define PW_FUNCTION_INLINE_CALLABLE_SIZE (2 * 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 + +namespace pw::function_internal::config { + +inline constexpr size_t kInlineCallableSize = PW_FUNCTION_INLINE_CALLABLE_SIZE; +inline constexpr bool kEnableDynamicAllocation = + PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION; + +} // namespace pw::function_internal::config
diff --git a/pw_function/public/pw_function/function.h b/pw_function/public/pw_function/function.h new file mode 100644 index 0000000..ea086c4 --- /dev/null +++ b/pw_function/public/pw_function/function.h
@@ -0,0 +1,73 @@ +// 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 "pw_function/internal/function.h" + +namespace pw { + +// pw::Function is a wrapper for an aribtrary callable object. It can be used by +// callback-based APIs to allow callers to provide any type of callable. +// +// Example: +// +// template <typename T> +// bool All(const pw::Vector<T>& items, +// pw::Function<bool(const T& item)> predicate) { +// for (const T& item : items) { +// if (!predicate(item)) { +// return false; +// } +// } +// return true; +// } +// +// bool ElementsArePostive(const pw::Vector<int>& items) { +// return All(items, [](const int& i) { return i > 0; }); +// } +// +// bool IsEven(const int& i) { return i % 2 == 0; } +// +// bool ElementsAreEven(const pw::Vector<int>& items) { +// return All(items, IsEven); +// } +// +template <typename T> +using Function = function_internal::Function<T>; + +// A Closure is a function that does not take any arguments and returns nothing. +using Closure = Function<void()>; + +// nullptr comparisions for functions. +template <typename T> +bool operator==(const Function<T>& f, std::nullptr_t) { + return !f; +} + +template <typename T> +bool operator!=(const Function<T>& f, std::nullptr_t) { + return !!f; +} + +template <typename T> +bool operator==(std::nullptr_t, const Function<T>& f) { + return !f; +} + +template <typename T> +bool operator!=(std::nullptr_t, const Function<T>& f) { + return !!f; +} + +} // namespace pw
diff --git a/pw_function/public/pw_function/internal/function.h b/pw_function/public/pw_function/internal/function.h new file mode 100644 index 0000000..5bf5afa --- /dev/null +++ b/pw_function/public/pw_function/internal/function.h
@@ -0,0 +1,320 @@ +// 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 <cstddef> +#include <new> +#include <utility> + +#include "pw_assert/assert.h" +#include "pw_function/config.h" +#include "pw_preprocessor/compiler.h" + +namespace pw::function_internal { + +template <typename T, typename Comparison = bool> +struct NullEq { + static constexpr bool Test(const T&) { return false; } +}; + +// Partial specialization for values of T comparable to nullptr. +template <typename T> +struct NullEq<T, decltype(std::declval<T>() == 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(ignored, "-Waddress"); + static constexpr bool Test(const T& v) { return v == nullptr; } + PW_MODIFY_DIAGNOSTICS_POP(); +}; + +// Tests whether a value is considered to be null. +template <typename T> +static constexpr bool IsNull(const T& v) { + return NullEq<T>::Test(v); +} + +// FunctionTarget is an interface for storing a callable object and providing a +// way to invoke it. +template <typename Return, typename... Args> +class FunctionTarget { + public: + FunctionTarget() = default; + virtual ~FunctionTarget() = default; + + FunctionTarget(const FunctionTarget&) = delete; + FunctionTarget(FunctionTarget&&) = delete; + FunctionTarget& operator=(const FunctionTarget&) = delete; + FunctionTarget& operator=(FunctionTarget&&) = delete; + + virtual bool IsNull() const = 0; + + // Invoke the callable stored by the function target. + virtual Return operator()(Args&&... args) const = 0; + + // Move initialize the function target to a provided location. + virtual void MoveInitializeTo(void* ptr) = 0; +}; + +// A function target that does not store any callable. Attempting to invoke it +// results in a crash. +template <typename Return, typename... Args> +class NullFunctionTarget final : public FunctionTarget<Return, Args...> { + public: + NullFunctionTarget() = default; + ~NullFunctionTarget() final = default; + + NullFunctionTarget(const NullFunctionTarget&) = delete; + NullFunctionTarget(NullFunctionTarget&&) = delete; + NullFunctionTarget& operator=(const NullFunctionTarget&) = delete; + NullFunctionTarget& operator=(NullFunctionTarget&&) = delete; + + bool IsNull() const final { return true; } + + Return operator()(Args&&...) 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 <typename Callable, typename Return, typename... Args> +class InlineFunctionTarget final : public FunctionTarget<Return, Args...> { + public: + explicit InlineFunctionTarget(Callable&& callable) + : callable_(std::move(callable)) {} + + ~InlineFunctionTarget() final = default; + + InlineFunctionTarget(const InlineFunctionTarget&) = delete; + InlineFunctionTarget& operator=(const InlineFunctionTarget&) = delete; + + InlineFunctionTarget(InlineFunctionTarget&& other) + : callable_(std::move(other.callable_)) {} + InlineFunctionTarget& operator=(InlineFunctionTarget&&) = default; + + bool IsNull() const final { return false; } + + Return operator()(Args&&... args) const final { + return callable_(std::forward<Args>(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 <typename Callable, typename Return, typename... Args> +class MemoryFunctionTarget final : public FunctionTarget<Return, Args...> { + public: + MemoryFunctionTarget(void* address, Callable&& callable) : address_(address) { + new (address_) Callable(std::move(callable)); + } + + ~MemoryFunctionTarget() 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; + + bool IsNull() const final { return false; } + + Return operator()(Args&&... args) const final { + return callable()(std::forward<Args>(args)...); + } + + void MoveInitializeTo(void* ptr) final { + new (ptr) MemoryFunctionTarget(std::move(*this)); + } + + private: + Callable& callable() { + return *std::launder(reinterpret_cast<Callable*>(address_)); + } + const Callable& callable() const { + return *std::launder(reinterpret_cast<const Callable*>(address_)); + } + + void* address_; +}; + +template <size_t kSizeBytes> +using FunctionStorage = + std::aligned_storage_t<kSizeBytes, alignof(std::max_align_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 <size_t kSizeBytes, typename Return, typename... Args> +class FunctionTargetHolder { + public: + FunctionTargetHolder() = default; + + FunctionTargetHolder(const FunctionTargetHolder&) = delete; + FunctionTargetHolder(FunctionTargetHolder&&) = delete; + FunctionTargetHolder& operator=(const FunctionTargetHolder&) = delete; + FunctionTargetHolder& operator=(FunctionTargetHolder&&) = delete; + + constexpr void InitializeNullTarget() { + using NullFunctionTarget = NullFunctionTarget<Return, Args...>; + static_assert(sizeof(NullFunctionTarget) <= kSizeBytes, + "NullFunctionTarget must fit within FunctionTargetHolder"); + new (&bits_) NullFunctionTarget; + } + + // Initializes an InlineFunctionTarget with the callable, failing if it is too + // large. + template <typename Callable> + void InitializeInlineTarget(Callable callable) { + using InlineFunctionTarget = + InlineFunctionTarget<Callable, Return, Args...>; + 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 <typename Callable> + void InitializeMemoryTarget(Callable callable, void* storage) { + using MemoryFunctionTarget = + MemoryFunctionTarget<Callable, Return, Args...>; + static_assert(sizeof(MemoryFunctionTarget) <= kSizeBytes, + "MemoryFunctionTarget must fit within FunctionTargetHolder"); + new (&bits_) MemoryFunctionTarget(storage, std::move(callable)); + } + + void DestructTarget() { target().~Target(); } + + // 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<Return, Args...>; + Target& target() { return *std::launder(reinterpret_cast<Target*>(&bits_)); } + const Target& target() const { + return *std::launder(reinterpret_cast<const Target*>(&bits_)); + } + + private: + // Storage for an implementation of the FunctionTarget interface. + FunctionStorage<kSizeBytes> bits_; +}; + +template <typename Return, typename... Args> +class Function; + +template <typename Return, typename... Args> +class Function<Return(Args...)> { + public: + constexpr Function() { holder_.InitializeNullTarget(); } + constexpr Function(std::nullptr_t) : Function() {} + + template <typename Callable> + Function(Callable callable) { + if (IsNull(callable)) { + holder_.InitializeNullTarget(); + } else { + holder_.InitializeInlineTarget(std::move(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(); + InitializeTarget(std::move(callable)); + return *this; + } + + ~Function() { holder_.DestructTarget(); } + + Return operator()(Args&&... args) const { + return holder_.target()(std::forward<Args>(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, 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 (IsNull(callable)) { + holder_.InitializeNullTarget(); + } else { + holder_.InitializeMemoryTarget(std::move(callable), storage); + } + } + + FunctionTargetHolder<config::kInlineCallableSize, Return, Args...> holder_; +}; + +} // namespace pw::function_internal
diff --git a/pw_function/size_report/BUILD b/pw_function/size_report/BUILD new file mode 100644 index 0000000..072ec6f --- /dev/null +++ b/pw_function/size_report/BUILD
@@ -0,0 +1,49 @@ +# 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. + +load( + "//pw_build:pigweed.bzl", + "pw_cc_binary", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache License 2.0 + +pw_cc_binary( + name = "pointer_base", + srcs = ["pointer_base.cc"], + deps = [ + "//pw_bloat:bloat_this_binary", + "//pw_function", + ], +) + +pw_cc_binary( + name = "basic_function", + srcs = ["basic_function.cc"], + deps = [ + "//pw_bloat:bloat_this_binary", + "//pw_function", + ], +) + +pw_cc_binary( + name = "callable_size", + srcs = ["callable_size.cc"], + deps = [ + "//pw_bloat:bloat_this_binary", + "//pw_function", + ], +)
diff --git a/pw_function/size_report/BUILD.gn b/pw_function/size_report/BUILD.gn new file mode 100644 index 0000000..232fbb9 --- /dev/null +++ b/pw_function/size_report/BUILD.gn
@@ -0,0 +1,74 @@ +# 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. + +import("//build_overrides/pigweed.gni") + +import("$dir_pw_build/target_types.gni") + +_deps = [ + "$dir_pw_bloat:bloat_this_binary", + "..:pw_function", +] + +pw_executable("pointer_base") { + sources = [ "pointer_base.cc" ] + deps = _deps +} + +pw_executable("basic_function") { + sources = [ "basic_function.cc" ] + deps = _deps +} + +pw_executable("callable_size_base") { + sources = [ "callable_size.cc" ] + defines = [ "_BASE=1" ] + deps = _deps +} + +pw_executable("callable_size_function_pointer") { + sources = [ "callable_size.cc" ] + defines = [ "_FUNCTION_POINTER=1" ] + deps = _deps +} + +pw_executable("callable_size_static_lambda") { + sources = [ "callable_size.cc" ] + defines = [ "_STATIC_LAMBDA=1" ] + deps = _deps +} + +pw_executable("callable_size_simple_lambda") { + sources = [ "callable_size.cc" ] + defines = [ "_SIMPLE_LAMBDA=1" ] + deps = _deps +} + +pw_executable("callable_size_capturing_lambda") { + sources = [ "callable_size.cc" ] + defines = [ "_CAPTURING_LAMBDA=1" ] + deps = _deps +} + +pw_executable("callable_size_multi_capturing_lambda") { + sources = [ "callable_size.cc" ] + defines = [ "_MULTI_CAPTURING_LAMBDA=1" ] + deps = _deps +} + +pw_executable("callable_size_custom_class") { + sources = [ "callable_size.cc" ] + defines = [ "_CUSTOM_CLASS=1" ] + deps = _deps +}
diff --git a/pw_function/size_report/basic_function.cc b/pw_function/size_report/basic_function.cc new file mode 100644 index 0000000..14f7ea9 --- /dev/null +++ b/pw_function/size_report/basic_function.cc
@@ -0,0 +1,31 @@ +// 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. + +#include "pw_function/function.h" + +namespace { + +int volatile* unoptimizable; + +void DoTheThing(int value, pw::Function<void(int)> callback) { + callback(value ^ *unoptimizable); +} + +} // namespace + +int main() { + int result; + DoTheThing(3, [&](int r) { result = r; }); + return result; +}
diff --git a/pw_function/size_report/callable_size.cc b/pw_function/size_report/callable_size.cc new file mode 100644 index 0000000..3b00b24 --- /dev/null +++ b/pw_function/size_report/callable_size.cc
@@ -0,0 +1,73 @@ +// 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. + +#include <array> +#include <cstddef> + +namespace { + +int volatile* unoptimizable; + +template <typename Callable> +class CallableSize { + public: + constexpr CallableSize(Callable callable) : callable_(std::move(callable)) {} + + int PreventOptimization() { return *unoptimizable; } + + private: + alignas(std::max_align_t) Callable callable_; +}; + +[[maybe_unused]] void Function() {} + +class CustomCallableClass { + public: + void operator()() {} + + private: + std::array<std::byte, 16> data_; +}; + +} // namespace + +int main() { + int a = 0; + int b = 1; + int c = 2; + int d = 3; + static_cast<void>(a); + static_cast<void>(b); + static_cast<void>(c); + static_cast<void>(d); + +#if defined(_BASE) + CallableSize<std::array<std::byte, 0>> callable_size({}); +#elif defined(_FUNCTION_POINTER) + static CallableSize callable_size(Function); +#elif defined(_STATIC_LAMBDA) + static CallableSize callable_size(+[]() {}); +#elif defined(_SIMPLE_LAMBDA) + static CallableSize callable_size([]() {}); +#elif defined(_CAPTURING_LAMBDA) + static CallableSize callable_size([a]() {}); +#elif defined(_MULTI_CAPTURING_LAMBDA) + static CallableSize callable_size([a, b, c, d]() {}); +#elif defined(_CUSTOM_CLASS) + static CallableSize callable_size((CustomCallableClass())); +#endif + + int foo = callable_size.PreventOptimization(); + return sizeof(callable_size) + foo; +}
diff --git a/pw_function/size_report/pointer_base.cc b/pw_function/size_report/pointer_base.cc new file mode 100644 index 0000000..581d6ce --- /dev/null +++ b/pw_function/size_report/pointer_base.cc
@@ -0,0 +1,32 @@ +// 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. + +namespace { + +int volatile* unoptimizable; + +void DoTheThing(int value, void (*callback)(int)) { + callback(value ^ *unoptimizable); +} + +int thing_result; + +void Callback(int result) { thing_result = result; } + +} // namespace + +int main() { + DoTheThing(3, Callback); + return thing_result; +}