libcc1: use variadic templates for callbacks

This patch completes the transition of libcc1 from the use of separate
template functions for different arities to the use of variadic
functions.  This is how I had wanted it to work from the very
beginning, and is possible now with C++11.

I had thought that variadic callbacks required C++17, but it turns out
that the approach taken here is basically equivalent to std::apply --
just a bit wordier.

libcc1

	* rpc.hh (argument_wrapper) <get>: Replace cast operator.
	(argument_wrapper<T *>) <get>: Likewise.
	(unmarshall): Add std::tuple overloads.
	(callback): Remove.
	(class invoker): New.
	* libcp1plugin.cc (plugin_init): Update.
	* libcp1.cc (libcp1::add_callbacks): Update.
	* libcc1plugin.cc (plugin_init): Update.
	* libcc1.cc (libcc1::add_callbacks): Update.
	* connection.cc (cc1_plugin::connection::do_wait): Update.
This commit is contained in:
Tom Tromey 2021-05-04 15:26:58 -06:00
parent ee75ca6b72
commit 8fdffa48c5
6 changed files with 88 additions and 202 deletions

View File

@ -129,7 +129,7 @@ cc1_plugin::connection::do_wait (bool want_result)
return FAIL; return FAIL;
callback_ftype *callback callback_ftype *callback
= m_callbacks.find_callback (method_name); = m_callbacks.find_callback (method_name.get ());
// The call to CALLBACK is where we may end up in a // The call to CALLBACK is where we may end up in a
// reentrant call. // reentrant call.
if (callback == NULL || !callback (this)) if (callback == NULL || !callback (this))

View File

@ -143,15 +143,13 @@ void
libcc1::add_callbacks () libcc1::add_callbacks ()
{ {
cc1_plugin::callback_ftype *fun cc1_plugin::callback_ftype *fun
= cc1_plugin::callback<int, = cc1_plugin::invoker<int,
enum gcc_c_oracle_request, enum gcc_c_oracle_request,
const char *, const char *>::invoke<c_call_binding_oracle>;
c_call_binding_oracle>;
connection->add_callback ("binding_oracle", fun); connection->add_callback ("binding_oracle", fun);
fun = cc1_plugin::callback<gcc_address, fun = cc1_plugin::invoker<gcc_address,
const char *, const char *>::invoke<c_call_symbol_address>;
c_call_symbol_address>;
connection->add_callback ("address_oracle", fun); connection->add_callback ("address_oracle", fun);
} }

View File

@ -762,46 +762,46 @@ plugin_init (struct plugin_name_args *plugin_info,
#define GCC_METHOD0(R, N) \ #define GCC_METHOD0(R, N) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, plugin_ ## N>; \ = cc1_plugin::invoker<R>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD1(R, N, A) \ #define GCC_METHOD1(R, N, A) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, plugin_ ## N>; \ = cc1_plugin::invoker<R, A>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD2(R, N, A, B) \ #define GCC_METHOD2(R, N, A, B) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, plugin_ ## N>; \ = cc1_plugin::invoker<R, A, B>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD3(R, N, A, B, C) \ #define GCC_METHOD3(R, N, A, B, C) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, C, plugin_ ## N>; \ = cc1_plugin::invoker<R, A, B, C>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD4(R, N, A, B, C, D) \ #define GCC_METHOD4(R, N, A, B, C, D) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, C, D, \ = cc1_plugin::invoker<R, A, B, C, \
plugin_ ## N>; \ D>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD5(R, N, A, B, C, D, E) \ #define GCC_METHOD5(R, N, A, B, C, D, E) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, C, D, E, \ = cc1_plugin::invoker<R, A, B, C, D, \
plugin_ ## N>; \ E>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD7(R, N, A, B, C, D, E, F, G) \ #define GCC_METHOD7(R, N, A, B, C, D, E, F, G) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, C, D, E, F, G, \ = cc1_plugin::invoker<R, A, B, C, D, \
plugin_ ## N>; \ E, F, G>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }

View File

@ -166,23 +166,18 @@ void
libcp1::add_callbacks () libcp1::add_callbacks ()
{ {
cc1_plugin::callback_ftype *fun cc1_plugin::callback_ftype *fun
= cc1_plugin::callback<int, = cc1_plugin::invoker<int, enum gcc_cp_oracle_request,
enum gcc_cp_oracle_request, const char *>::invoke<cp_call_binding_oracle>;
const char *,
cp_call_binding_oracle>;
connection->add_callback ("binding_oracle", fun); connection->add_callback ("binding_oracle", fun);
fun = cc1_plugin::callback<gcc_address, fun = cc1_plugin::invoker<gcc_address,
const char *, const char *>::invoke<cp_call_symbol_address>;
cp_call_symbol_address>;
connection->add_callback ("address_oracle", fun); connection->add_callback ("address_oracle", fun);
fun = cc1_plugin::callback<int, fun = cc1_plugin::invoker<int>::invoke<cp_call_enter_scope>;
cp_call_enter_scope>;
connection->add_callback ("enter_scope", fun); connection->add_callback ("enter_scope", fun);
fun = cc1_plugin::callback<int, fun = cc1_plugin::invoker<int>::invoke<cp_call_leave_scope>;
cp_call_leave_scope>;
connection->add_callback ("leave_scope", fun); connection->add_callback ("leave_scope", fun);
} }

View File

@ -3509,46 +3509,46 @@ plugin_init (struct plugin_name_args *plugin_info,
#define GCC_METHOD0(R, N) \ #define GCC_METHOD0(R, N) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, plugin_ ## N>; \ = cc1_plugin::invoker<R>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD1(R, N, A) \ #define GCC_METHOD1(R, N, A) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, plugin_ ## N>; \ = cc1_plugin::invoker<R, A>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD2(R, N, A, B) \ #define GCC_METHOD2(R, N, A, B) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, plugin_ ## N>; \ = cc1_plugin::invoker<R, A, B>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD3(R, N, A, B, C) \ #define GCC_METHOD3(R, N, A, B, C) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, C, plugin_ ## N>; \ = cc1_plugin::invoker<R, A, B, C>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD4(R, N, A, B, C, D) \ #define GCC_METHOD4(R, N, A, B, C, D) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, C, D, \ = cc1_plugin::invoker<R, A, B, C, \
plugin_ ## N>; \ D>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD5(R, N, A, B, C, D, E) \ #define GCC_METHOD5(R, N, A, B, C, D, E) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, C, D, E, \ = cc1_plugin::invoker<R, A, B, C, \
plugin_ ## N>; \ D, E>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }
#define GCC_METHOD7(R, N, A, B, C, D, E, F, G) \ #define GCC_METHOD7(R, N, A, B, C, D, E, F, G) \
{ \ { \
cc1_plugin::callback_ftype *fun \ cc1_plugin::callback_ftype *fun \
= cc1_plugin::callback<R, A, B, C, D, E, F, G, \ = cc1_plugin::invoker<R, A, B, C, \
plugin_ ## N>; \ D, E, F, G>::invoke<plugin_ ## N>; \
current_context->add_callback (# N, fun); \ current_context->add_callback (# N, fun); \
} }

View File

@ -43,7 +43,7 @@ namespace cc1_plugin
argument_wrapper (const argument_wrapper &) = delete; argument_wrapper (const argument_wrapper &) = delete;
argument_wrapper &operator= (const argument_wrapper &) = delete; argument_wrapper &operator= (const argument_wrapper &) = delete;
operator T () const { return m_object; } T get () const { return m_object; }
status unmarshall (connection *conn) status unmarshall (connection *conn)
{ {
@ -68,7 +68,7 @@ namespace cc1_plugin
typedef typename std::remove_const<T>::type type; typedef typename std::remove_const<T>::type type;
operator const type * () const const type *get () const
{ {
return m_object.get (); return m_object.get ();
} }
@ -88,17 +88,14 @@ namespace cc1_plugin
}; };
// There are two kinds of template functions here: "call" and // There are two kinds of template functions here: "call" and
// "callback". "call" is implemented with variadic templates, but // "invoker".
// "callback" is repeated multiple times to handle different numbers
// of arguments. (This could be improved with C++17 and
// std::apply.)
// The "call" template is used for making a remote procedure call. // The "call" template is used for making a remote procedure call.
// It starts a query ('Q') packet, marshalls its arguments, waits // It starts a query ('Q') packet, marshalls its arguments, waits
// for a result, and finally reads and returns the result via an // for a result, and finally reads and returns the result via an
// "out" parameter. // "out" parameter.
// The "callback" template is used when receiving a remote procedure // The "invoker" template is used when receiving a remote procedure
// call. This template function is suitable for use with the // call. This template function is suitable for use with the
// "callbacks" and "connection" classes. It decodes incoming // "callbacks" and "connection" classes. It decodes incoming
// arguments, passes them to the wrapped function, and finally // arguments, passes them to the wrapped function, and finally
@ -123,175 +120,71 @@ namespace cc1_plugin
return OK; return OK;
} }
template<typename R, R (*func) (connection *)> // The base case -- just return OK.
status template<int I, typename... T>
callback (connection *conn) typename std::enable_if<I == sizeof... (T), status>::type
unmarshall (connection *, std::tuple<T...> &)
{ {
R result; return OK;
if (!unmarshall_check (conn, 0))
return FAIL;
result = func (conn);
if (!conn->send ('R'))
return FAIL;
return marshall (conn, result);
} }
template<typename R, typename A, R (*func) (connection *, A)> // Unmarshall this argument, then unmarshall all subsequent args.
status template<int I, typename... T>
callback (connection *conn) typename std::enable_if<I < sizeof... (T), status>::type
unmarshall (connection *conn, std::tuple<T...> &value)
{ {
argument_wrapper<A> arg; if (!std::get<I> (value).unmarshall (conn))
R result;
if (!unmarshall_check (conn, 1))
return FAIL; return FAIL;
if (!arg.unmarshall (conn)) return unmarshall<I + 1, T...> (conn, value);
return FAIL;
result = func (conn, arg);
if (!conn->send ('R'))
return FAIL;
return marshall (conn, result);
} }
template<typename R, typename A1, typename A2, R (*func) (connection *, // Wrap a static function that is suitable for use as a callback.
A1, A2)> // This is a template function inside a template class to work
status // around limitations with multiple variadic packs.
callback (connection *conn) template<typename R, typename... Arg>
class invoker
{ {
argument_wrapper<A1> arg1; // Base case -- we can call the function.
argument_wrapper<A2> arg2; template<int I, R func (connection *, Arg...), typename... T>
R result; static typename std::enable_if<I == sizeof... (Arg), R>::type
call (connection *conn, const std::tuple<argument_wrapper<Arg>...> &,
T... args)
{
return func (conn, args...);
}
if (!unmarshall_check (conn, 2)) // Unpack one argument and continue the recursion.
return FAIL; template<int I, R func (connection *, Arg...), typename... T>
if (!arg1.unmarshall (conn)) static typename std::enable_if<I < sizeof... (Arg), R>::type
return FAIL; call (connection *conn, const std::tuple<argument_wrapper<Arg>...> &value,
if (!arg2.unmarshall (conn)) T... args)
return FAIL; {
result = func (conn, arg1, arg2); return call<I + 1, func> (conn, value, args...,
if (!conn->send ('R')) std::get<I> (value).get ());
return FAIL; }
return marshall (conn, result);
}
template<typename R, typename A1, typename A2, typename A3, public:
R (*func) (connection *, A1, A2, A3)>
status
callback (connection *conn)
{
argument_wrapper<A1> arg1;
argument_wrapper<A2> arg2;
argument_wrapper<A3> arg3;
R result;
if (!unmarshall_check (conn, 3)) // A callback function that reads arguments from the connection,
return FAIL; // calls the wrapped function, and then sends the result back on
if (!arg1.unmarshall (conn)) // the connection.
return FAIL; template<R func (connection *, Arg...)>
if (!arg2.unmarshall (conn)) static status
return FAIL; invoke (connection *conn)
if (!arg3.unmarshall (conn)) {
return FAIL; if (!unmarshall_check (conn, sizeof... (Arg)))
result = func (conn, arg1, arg2, arg3); return FAIL;
if (!conn->send ('R')) std::tuple<argument_wrapper<Arg>...> wrapped;
return FAIL; if (!unmarshall<0> (conn, wrapped))
return marshall (conn, result); return FAIL;
}
template<typename R, typename A1, typename A2, typename A3, typename A4, R result = call<0, func> (conn, wrapped);
R (*func) (connection *, A1, A2, A3, A4)>
status
callback (connection *conn)
{
argument_wrapper<A1> arg1;
argument_wrapper<A2> arg2;
argument_wrapper<A3> arg3;
argument_wrapper<A4> arg4;
R result;
if (!unmarshall_check (conn, 4)) if (!conn->send ('R'))
return FAIL; return FAIL;
if (!arg1.unmarshall (conn)) return marshall (conn, result);
return FAIL; }
if (!arg2.unmarshall (conn)) };
return FAIL;
if (!arg3.unmarshall (conn))
return FAIL;
if (!arg4.unmarshall (conn))
return FAIL;
result = func (conn, arg1, arg2, arg3, arg4);
if (!conn->send ('R'))
return FAIL;
return marshall (conn, result);
}
template<typename R, typename A1, typename A2, typename A3, typename A4,
typename A5, R (*func) (connection *, A1, A2, A3, A4, A5)>
status
callback (connection *conn)
{
argument_wrapper<A1> arg1;
argument_wrapper<A2> arg2;
argument_wrapper<A3> arg3;
argument_wrapper<A4> arg4;
argument_wrapper<A5> arg5;
R result;
if (!unmarshall_check (conn, 5))
return FAIL;
if (!arg1.unmarshall (conn))
return FAIL;
if (!arg2.unmarshall (conn))
return FAIL;
if (!arg3.unmarshall (conn))
return FAIL;
if (!arg4.unmarshall (conn))
return FAIL;
if (!arg5.unmarshall (conn))
return FAIL;
result = func (conn, arg1, arg2, arg3, arg4, arg5);
if (!conn->send ('R'))
return FAIL;
return marshall (conn, result);
}
template<typename R, typename A1, typename A2, typename A3, typename A4,
typename A5, typename A6, typename A7,
R (*func) (connection *, A1, A2, A3, A4, A5, A6, A7)>
status
callback (connection *conn)
{
argument_wrapper<A1> arg1;
argument_wrapper<A2> arg2;
argument_wrapper<A3> arg3;
argument_wrapper<A4> arg4;
argument_wrapper<A5> arg5;
argument_wrapper<A6> arg6;
argument_wrapper<A7> arg7;
R result;
if (!unmarshall_check (conn, 7))
return FAIL;
if (!arg1.unmarshall (conn))
return FAIL;
if (!arg2.unmarshall (conn))
return FAIL;
if (!arg3.unmarshall (conn))
return FAIL;
if (!arg4.unmarshall (conn))
return FAIL;
if (!arg5.unmarshall (conn))
return FAIL;
if (!arg6.unmarshall (conn))
return FAIL;
if (!arg7.unmarshall (conn))
return FAIL;
result = func (conn, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
if (!conn->send ('R'))
return FAIL;
return marshall (conn, result);
}
}; };
#endif // CC1_PLUGIN_RPC_HH #endif // CC1_PLUGIN_RPC_HH