diff options
author | Pedro Alves <palves@redhat.com> | 2017-02-23 16:14:08 +0000 |
---|---|---|
committer | Pedro Alves <palves@redhat.com> | 2017-02-23 16:14:08 +0000 |
commit | 07e253aa3b7a530f22b84053e661842ccd9da2ea (patch) | |
tree | 20dc7cea2b67f76f68844e81e5c035fb8413a551 | |
parent | 1e9d41d49f7f0b9e7381e8bf8ce848f8a33b8fde (diff) | |
download | gdb-07e253aa3b7a530f22b84053e661842ccd9da2ea.zip gdb-07e253aa3b7a530f22b84053e661842ccd9da2ea.tar.gz gdb-07e253aa3b7a530f22b84053e661842ccd9da2ea.tar.bz2 |
Introduce gdb::function_view
This commit adds a new function_view type. This type holds a
non-owning reference to a callable. It is meant to be used as
callback type of functions, instead of using the C-style pair of
function pointer and 'void *data' arguments. function_view allows
passing references to stateful function objects / lambdas with
captures as callbacks efficiently, while function pointer + 'void *'
does not.
See the intro in the new function-view.h header for more.
Unit tests included, put into a new gdb/unittests/ subdir.
gdb/ChangeLog:
2017-02-23 Pedro Alves <palves@redhat.com>
* Makefile.in (SUBDIR_UNITTESTS_SRCS, SUBDIR_UNITTESTS_OBS): New.
(%.o) <unittests/%.c>: New pattern.
* configure.ac ($development): Add $(SUBDIR_UNITTESTS_OBS) to
CONFIG_OBS, and $(SUBDIR_UNITTESTS_SRCS) to CONFIG_SRCS.
* common/function-view.h: New file.
* unittests/function-view-selftests.c: New file.
* configure: Regenerate.
-rw-r--r-- | gdb/ChangeLog | 10 | ||||
-rw-r--r-- | gdb/Makefile.in | 10 | ||||
-rw-r--r-- | gdb/common/function-view.h | 355 | ||||
-rwxr-xr-x | gdb/configure | 2 | ||||
-rw-r--r-- | gdb/configure.ac | 2 | ||||
-rw-r--r-- | gdb/unittests/function-view-selftests.c | 178 |
6 files changed, 557 insertions, 0 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 30cd576..77e7ef8 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,13 @@ +2017-02-23 Pedro Alves <palves@redhat.com> + + * Makefile.in (SUBDIR_UNITTESTS_SRCS, SUBDIR_UNITTESTS_OBS): New. + (%.o) <unittests/%.c>: New pattern. + * configure.ac ($development): Add $(SUBDIR_UNITTESTS_OBS) to + CONFIG_OBS, and $(SUBDIR_UNITTESTS_SRCS) to CONFIG_SRCS. + * common/function-view.h: New file. + * unittests/function-view-selftests.c: New file. + * configure: Regenerate. + 2017-02-23 Simon Marchi <simon.marchi@ericsson.com> * bsd-uthread.c (bsd_uthread_thread_alive): Use ptid instead of diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 43253d3..268c2c6 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -523,6 +523,12 @@ SUBDIR_PYTHON_DEPS = SUBDIR_PYTHON_LDFLAGS = SUBDIR_PYTHON_CFLAGS = +SUBDIR_UNITTESTS_SRCS = \ + unittests/function-view-selftests.c + +SUBDIR_UNITTESTS_OBS = \ + function-view-selftests.o + # Opcodes currently live in one of two places. Either they are in the # opcode library, typically ../opcodes, or they are in a header file # in INCLUDE_DIR. @@ -1909,6 +1915,10 @@ all: gdb$(EXEEXT) $(CONFIG_ALL) $(COMPILE) $< $(POSTCOMPILE) +%.o: ${srcdir}/unittests/%.c + $(COMPILE) $< + $(POSTCOMPILE) + # Specify an explicit rule for gdb/common/agent.c, to avoid a clash with the # object file generate by gdb/agent.c. common-agent.o: $(srcdir)/common/agent.c diff --git a/gdb/common/function-view.h b/gdb/common/function-view.h new file mode 100644 index 0000000..66a691b --- /dev/null +++ b/gdb/common/function-view.h @@ -0,0 +1,355 @@ +/* Copyright (C) 2017 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef COMMON_FUNCTION_VIEW_H +#define COMMON_FUNCTION_VIEW_H + +/* function_view is a polymorphic type-erasing wrapper class that + encapsulates a non-owning reference to arbitrary callable objects. + + A way to put it is that function_view is to std::function like + std::string_view is to std::string. While std::function stores a + type-erased callable object internally, function_view holds a + type-erased reference to an external callable object. + + This is meant to be used as callback type of a function that: + + #1 - Takes a callback as parameter. + + #2 - Wants to support arbitrary callable objects as callback type + (e.g., stateful function objects, lambda closures, free + functions). + + #3 - Does not store the callback anywhere; instead the function + just calls the callback directly or forwards it to some + other function that calls it. + + #4 - Can't be, or we don't want it to be, a template function + with the callable type as template parameter. For example, + when the callback is a parameter of a virtual member + function, or when putting the function template in a header + would expose too much implementation detail. + + Note that the C-style "function pointer" + "void *data" callback + parameter idiom fails requirement #2 above. Please don't add new + uses of that idiom. I.e., something like this wouldn't work; + + typedef bool (iterate_over_foos_cb) (foo *f, void *user_data), + void iterate_over_foos (iterate_over_foos_cb *callback, void *user_data); + + foo *find_foo_by_type (int type) + { + foo *found = nullptr; + + iterate_over_foos ([&] (foo *f, void *data) + { + if (foo->type == type) + { + found = foo; + return true; // stop iterating + } + return false; // continue iterating + }, NULL); + + return found; + } + + The above wouldn't compile, because lambdas with captures can't be + implicitly converted to a function pointer (because a capture means + some context data must be passed to the lambda somehow). + + C++11 gave us std::function as type-erased wrapper around arbitrary + callables, however, std::function is not an ideal fit for transient + callbacks such as the use case above. For this use case, which is + quite pervasive, a function_view is a better choice, because while + function_view is light and does not require any heap allocation, + std::function is a heavy-weight object with value semantics that + generally requires a heap allocation on construction/assignment of + the target callable. In addition, while it is possible to use + std::function in such a way that avoids most of the overhead by + making sure to only construct it with callables of types that fit + std::function's small object optimization, such as function + pointers and std::reference_wrapper callables, that is quite + inconvenient in practice, because restricting to free-function + callables would imply no state/capture/closure, which we need in + most cases, and std::reference_wrapper implies remembering to use + std::ref/std::cref where the callable is constructed, with the + added inconvenience that std::ref/std::cref have deleted rvalue-ref + overloads, meaning you can't use unnamed/temporary lambdas with + them. + + Note that because function_view is a non-owning view of a callable, + care must be taken to ensure that the callable outlives the + function_view that calls it. This is not really a problem for the + use case function_view is intended for, such as passing a temporary + function object / lambda to a function that accepts a callback, + because in those cases, the temporary is guaranteed to be live + until the called function returns. + + Calling a function_view with no associated target is undefined, + unlike with std::function, which throws std::bad_function_call. + This is by design, to avoid the otherwise necessary NULL check in + function_view::operator(). + + Since function_view objects are small (a pair of pointers), they + should generally be passed around by value. + + Usage: + + Given this function that accepts a callback: + + void + iterate_over_foos (gdb::function_view<void (foo *)> callback) + { + for (auto &foo : foos) + callback (&foo); + } + + you can call it like this, passing a lambda as callback: + + iterate_over_foos ([&] (foo *f) + { + process_one_foo (f); + }); + + or like this, passing a function object as callback: + + struct function_object + { + void operator() (foo *f) + { + if (s->check ()) + process_one_foo (f); + } + + // some state + state *s; + }; + + state mystate; + function_object matcher {&mystate}; + iterate_over_foos (matcher); + + or like this, passing a function pointer as callback: + + iterate_over_foos (process_one_foo); + + You can find unit tests covering the whole API in + unittests/function-view-selftests.c. */ + +namespace gdb { + +namespace traits { + /* A few trait helpers. */ + template<typename Predicate> + struct Not : public std::integral_constant<bool, !Predicate::value> + {}; + + template<typename...> + struct Or; + + template<> + struct Or<> : public std::false_type + {}; + + template<typename B1> + struct Or<B1> : public B1 + {}; + + template<typename B1, typename B2> + struct Or<B1, B2> + : public std::conditional<B1::value, B1, B2>::type + {}; + + template<typename B1,typename B2,typename B3, typename... Bn> + struct Or<B1, B2, B3, Bn...> + : public std::conditional<B1::value, B1, Or<B2, B3, Bn...>>::type + {}; +} /* namespace traits */ + +namespace fv_detail { +/* Bits shared by all function_view instantiations that do not depend + on the template parameters. */ + +/* Storage for the erased callable. This is a union in order to be + able to save both a function object (data) pointer or a function + pointer without triggering undefined behavior. */ +union erased_callable +{ + /* For function objects. */ + void *data; + + /* For function pointers. */ + void (*fn) (); +}; + +} /* namespace fv_detail */ + +/* Use partial specialization to get access to the callable's + signature. */ +template<class Signature> +struct function_view; + +template<typename Res, typename... Args> +class function_view<Res (Args...)> +{ + template<typename From, typename To> + using CompatibleReturnType + = traits::Or<std::is_void<To>, + std::is_same<From, To>, + std::is_convertible<From, To>>; + + /* True if Func can be called with Args, and either the result is + Res, convertible to Res or Res is void. */ + template<typename Callable, + typename Res2 = typename std::result_of<Callable &(Args...)>::type> + struct IsCompatibleCallable : CompatibleReturnType<Res2, Res> + {}; + + /* True if Callable is a function_view. Used to avoid hijacking the + copy ctor. */ + template <typename Callable> + struct IsFunctionView + : std::is_same<function_view, typename std::decay<Callable>::type> + {}; + + /* Helper to make SFINAE logic easier to read. */ + template<typename Condition> + using Requires = typename std::enable_if<Condition::value, void>::type; + + public: + + /* NULL by default. */ + constexpr function_view () noexcept + : m_erased_callable {}, + m_invoker {} + {} + + /* Default copy/assignment is fine. */ + function_view (const function_view &) = default; + function_view &operator= (const function_view &) = default; + + /* This is the main entry point. Use SFINAE to avoid hijacking the + copy constructor and to ensure that the target type is + compatible. */ + template + <typename Callable, + typename = Requires<traits::Not<IsFunctionView<Callable>>>, + typename = Requires<IsCompatibleCallable<Callable>>> + function_view (Callable &&callable) noexcept + { + bind (callable); + } + + /* Construct a NULL function_view. */ + constexpr function_view (std::nullptr_t) noexcept + : m_erased_callable {}, + m_invoker {} + {} + + /* Clear a function_view. */ + function_view &operator= (std::nullptr_t) noexcept + { + m_invoker = nullptr; + return *this; + } + + /* Return true if the wrapper has a target, false otherwise. Note + we check M_INVOKER instead of M_ERASED_CALLABLE because we don't + know which member of the union is active right now. */ + constexpr explicit operator bool () const noexcept + { return m_invoker != nullptr; } + + /* Call the callable. */ + Res operator () (Args... args) const + { return m_invoker (m_erased_callable, std::forward<Args> (args)...); } + + private: + + /* Bind this function_view to a compatible function object + reference. */ + template <typename Callable> + void bind (Callable &callable) noexcept + { + m_erased_callable.data = (void *) std::addressof (callable); + m_invoker = [] (fv_detail::erased_callable ecall, Args... args) + noexcept (noexcept (callable (std::forward<Args> (args)...))) -> Res + { + auto &restored_callable = *static_cast<Callable *> (ecall.data); + /* The explicit cast to Res avoids a compile error when Res is + void and the callable returns non-void. */ + return (Res) restored_callable (std::forward<Args> (args)...); + }; + } + + /* Bind this function_view to a compatible function pointer. + + Making this a separate function allows avoiding one indirection, + by storing the function pointer directly in the storage, instead + of a pointer to pointer. erased_callable is then a union in + order to avoid storing a function pointer as a data pointer here, + which would be undefined. */ + template<class Res2, typename... Args2> + void bind (Res2 (*fn) (Args2...)) noexcept + { + m_erased_callable.fn = reinterpret_cast<void (*) ()> (fn); + m_invoker = [] (fv_detail::erased_callable ecall, Args... args) + noexcept (noexcept (fn (std::forward<Args> (args)...))) -> Res + { + auto restored_fn = reinterpret_cast<Res2 (*) (Args2...)> (ecall.fn); + /* The explicit cast to Res avoids a compile error when Res is + void and the callable returns non-void. */ + return (Res) restored_fn (std::forward<Args> (args)...); + }; + } + + /* Storage for the erased callable. */ + fv_detail::erased_callable m_erased_callable; + + /* The invoker. This is set to a capture-less lambda by one of the + 'bind' overloads. The lambda restores the right type of the + callable (which is passed as first argument), and forwards the + args. */ + Res (*m_invoker) (fv_detail::erased_callable, Args...); +}; + +/* Allow comparison with NULL. Defer the work to the in-class + operator bool implementation. */ + +template<typename Res, typename... Args> +constexpr inline bool +operator== (const function_view<Res (Args...)> &f, std::nullptr_t) noexcept +{ return !static_cast<bool> (f); } + +template<typename Res, typename... Args> +constexpr inline bool +operator== (std::nullptr_t, const function_view<Res (Args...)> &f) noexcept +{ return !static_cast<bool> (f); } + +template<typename Res, typename... Args> +constexpr inline bool +operator!= (const function_view<Res (Args...)> &f, std::nullptr_t) noexcept +{ return static_cast<bool> (f); } + +template<typename Res, typename... Args> +constexpr inline bool +operator!= (std::nullptr_t, const function_view<Res (Args...)> &f) noexcept +{ return static_cast<bool> (f); } + +} /* namespace gdb */ + +#endif diff --git a/gdb/configure b/gdb/configure index 6df88d9..7fbae9f 100755 --- a/gdb/configure +++ b/gdb/configure @@ -17410,6 +17410,8 @@ if $development; then $as_echo "#define GDB_SELF_TEST 1" >>confdefs.h + CONFIG_OBS="$CONFIG_OBS \$(SUBDIR_UNITTESTS_OBS)" + CONFIG_SRCS="$CONFIG_SRCS \$(SUBDIR_UNITTESTS_SRCS)" fi diff --git a/gdb/configure.ac b/gdb/configure.ac index d2d854d..50f6f59 100644 --- a/gdb/configure.ac +++ b/gdb/configure.ac @@ -2350,6 +2350,8 @@ AC_DEFINE(GDB_DEFAULT_HOST_CHARSET, "UTF-8", if $development; then AC_DEFINE(GDB_SELF_TEST, 1, [Define if self-testing features should be enabled]) + CONFIG_OBS="$CONFIG_OBS \$(SUBDIR_UNITTESTS_OBS)" + CONFIG_SRCS="$CONFIG_SRCS \$(SUBDIR_UNITTESTS_SRCS)" fi GDB_AC_TRANSFORM([gdb], [GDB_TRANSFORM_NAME]) diff --git a/gdb/unittests/function-view-selftests.c b/gdb/unittests/function-view-selftests.c new file mode 100644 index 0000000..310f2ad --- /dev/null +++ b/gdb/unittests/function-view-selftests.c @@ -0,0 +1,178 @@ +/* Self tests for function_view for GDB, the GNU debugger. + + Copyright (C) 2017 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "defs.h" +#include "selftest.h" +#include "common/function-view.h" + +namespace selftests { +namespace function_view { + +static int +plus_one_fn_int (int val) +{ + return ++val; +} + +static short +plus_one_fn_short (short val) +{ + return ++val; +} + +static int +call_callback_int (int val, gdb::function_view <int (int)> callback) +{ + return callback (val); +} + +static void +call_callback_void (int val, gdb::function_view <void (int)> callback) +{ + callback (val); +} + +struct plus_one_int_func_obj +{ + int operator () (int val) + { + ++call_count; + return ++val; + } + + /* Number of times called. */ + int call_count = 0; +}; + +static void +run_tests () +{ + /* A simple lambda. */ + auto plus_one_lambda = [] (int val) { return ++val; }; + + /* A function_view that references the lambda. */ + gdb::function_view<int (int)> plus_one_func_view (plus_one_lambda); + + /* Check calling the lambda directly. */ + SELF_CHECK (plus_one_lambda (0) == 1); + SELF_CHECK (plus_one_lambda (1) == 2); + + /* Check calling lambda via the view. */ + SELF_CHECK (plus_one_func_view (2) == 3); + SELF_CHECK (plus_one_func_view (3) == 4); + + /* Check calling a function that takes a function_view as argument, + by value. Pass a lambda, making sure a function_view is properly + constructed implicitly. */ + SELF_CHECK (call_callback_int (1, [] (int val) + { + return val + 2; + }) == 3); + + /* Same, passing a named/lvalue lambda. */ + SELF_CHECK (call_callback_int (1, plus_one_lambda) == 2); + /* Same, passing a named/lvalue function_view (should copy). */ + SELF_CHECK (call_callback_int (1, plus_one_func_view) == 2); + + /* Check constructing a function view over a function-object + callable, and calling it. */ + plus_one_int_func_obj func_obj; + SELF_CHECK (func_obj (0) == 1); + SELF_CHECK (call_callback_int (1, func_obj) == 2); + /* Check that the callable was referenced, not copied. */ + SELF_CHECK (func_obj.call_count == 2); + + /* Check constructing a function_view over a free-function callable, + and calling it. */ + SELF_CHECK (call_callback_int (1, plus_one_fn_int) == 2); + + /* Check calling a function with a + compatible-but-not-exactly-the-same prototype. */ + SELF_CHECK (call_callback_int (1, [] (short val) -> short + { + return val + 2; + }) == 3); + /* Same, but passing a function pointer. */ + SELF_CHECK (call_callback_int (1, plus_one_fn_short) == 2); + + /* Like std::function, a function_view that expects a void return + can reference callables with non-void return type. The result is + simply discarded. Check a lambda, function object and a function + pointer. */ + call_callback_void (1, [] (int val) -> int + { + return val + 2; + }); + call_callback_void (1, func_obj); + call_callback_void (1, plus_one_fn_int); + + /* Check that the main ctor doesn't hijack the copy ctor. */ + auto plus_one_func_view2 (plus_one_func_view); + auto plus_one_func_view3 (plus_one_func_view2); + static_assert (std::is_same<decltype (plus_one_func_view), + decltype (plus_one_func_view2)>::value, ""); + static_assert (std::is_same<decltype (plus_one_func_view), + decltype (plus_one_func_view3)>::value, ""); + + SELF_CHECK (plus_one_func_view3 (1) == 2); + + /* Likewise, but propagate a NULL callable. If this calls the main + function_view ctor instead of the copy ctor by mistake, then + null_func_2 ends up non-NULL (because it'd instead reference + null_func_1 as just another callable). */ + constexpr gdb::function_view<int (int)> null_func_view_1 = nullptr; + constexpr auto null_func_view_2 (null_func_view_1); + + /* While at it, check whether the function_view is bound using + various forms, op==, op!= and op bool. */ + + /* op== */ + static_assert (null_func_view_2 == nullptr, ""); + static_assert (nullptr == null_func_view_2, ""); + static_assert (null_func_view_2 == NULL, ""); + static_assert (NULL == null_func_view_2, ""); + + /* op!= */ + static_assert (!(null_func_view_2 != nullptr), ""); + static_assert (!(nullptr != null_func_view_2), ""); + static_assert (!(null_func_view_2 != NULL), ""); + static_assert (!(NULL != null_func_view_2), ""); + + /* op bool */ + static_assert (!null_func_view_2, ""); + + /* Check the nullptr_t ctor. */ + constexpr gdb::function_view<int (int)> check_ctor_nullptr (nullptr); + static_assert (!check_ctor_nullptr, ""); + + /* Check the nullptr_t op= */ + gdb::function_view<int (int)> check_op_eq_null (plus_one_fn_int); + SELF_CHECK (check_op_eq_null); + check_op_eq_null = nullptr; + SELF_CHECK (!check_op_eq_null); +} + +} /* namespace function_view */ +} /* namespace selftests */ + +void +_initialize_function_view_selftests () +{ + register_self_test (selftests::function_view::run_tests); +} |