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/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;
+}