| //===-- Helper functions for client / server dispatch -----------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #ifndef LLVM_LIBC_SHARED_RPC_DISPATCH_H | |
| #define LLVM_LIBC_SHARED_RPC_DISPATCH_H | |
| #include "rpc.h" | |
| #include "rpc_util.h" | |
| namespace rpc { | |
| namespace { | |
| // Forward declarations needed for the server, we assume these are present. | |
| extern "C" void *malloc(__SIZE_TYPE__); | |
| extern "C" void free(void *); | |
| // Traits to convert between a tuple and binary representation of an argument | |
| // list. | |
| template <typename... Ts> struct tuple_bytes { | |
| static constexpr uint64_t SIZE = rpc::max(1ul, (0 + ... + sizeof(Ts))); | |
| using array_type = rpc::array<uint8_t, SIZE>; | |
| template <uint64_t... Is> | |
| RPC_ATTRS static constexpr array_type pack_impl(rpc::tuple<Ts...> t, | |
| rpc::index_sequence<Is...>) { | |
| array_type out{}; | |
| uint8_t *p = out.data(); | |
| ((rpc::rpc_memcpy(p, &rpc::get<Is>(t), sizeof(Ts)), p += sizeof(Ts)), ...); | |
| return out; | |
| } | |
| RPC_ATTRS static constexpr array_type pack(rpc::tuple<Ts...> t) { | |
| return pack_impl(t, rpc::index_sequence_for<Ts...>{}); | |
| } | |
| template <uint64_t... Is> | |
| RPC_ATTRS static constexpr rpc::tuple<Ts...> | |
| unpack_impl(const uint8_t *data, rpc::index_sequence<Is...>) { | |
| rpc::tuple<Ts...> t{}; | |
| const uint8_t *p = data; | |
| ((rpc::rpc_memcpy(&rpc::get<Is>(t), p, sizeof(Ts)), p += sizeof(Ts)), ...); | |
| return t; | |
| } | |
| RPC_ATTRS static constexpr rpc::tuple<Ts...> unpack(const array_type &a) { | |
| return unpack_impl(a.data(), rpc::index_sequence_for<Ts...>{}); | |
| } | |
| }; | |
| template <typename... Ts> | |
| struct tuple_bytes<rpc::tuple<Ts...>> : tuple_bytes<Ts...> {}; | |
| // Whether a pointer value will be marshalled between the client and server. | |
| template <typename Ty, typename PtrTy = rpc::remove_reference_t<Ty>, | |
| typename ElemTy = rpc::remove_pointer_t<PtrTy>> | |
| RPC_ATTRS constexpr bool is_marshalled_ptr_v = | |
| rpc::is_pointer_v<PtrTy> && rpc::is_complete_v<ElemTy> && | |
| !rpc::is_void_v<ElemTy>; | |
| // Get an index value for the marshalled types in the tuple type. | |
| template <class Tuple, uint64_t... Is> | |
| constexpr uint64_t marshalled_index(rpc::index_sequence<Is...>) { | |
| return (0u + ... + | |
| (rpc::is_marshalled_ptr_v<rpc::tuple_element_t<Is, Tuple>>)); | |
| } | |
| template <class Tuple, uint64_t I> | |
| constexpr uint64_t marshalled_index_v = | |
| marshalled_index<Tuple>(rpc::make_index_sequence<I>{}); | |
| // Storage for the marshalled arguments from the client. | |
| template <uint32_t NUM_LANES, typename... Ts> struct MarshalledState { | |
| static constexpr uint32_t NUM_PTRS = | |
| rpc::marshalled_index_v<rpc::tuple<Ts...>, sizeof...(Ts)>; | |
| uint64_t sizes[NUM_PTRS][NUM_LANES]{}; | |
| void *ptrs[NUM_PTRS][NUM_LANES]{}; | |
| }; | |
| template <uint32_t NUM_LANES, typename... Ts> | |
| struct MarshalledState<NUM_LANES, rpc::tuple<Ts...>> | |
| : MarshalledState<NUM_LANES, Ts...> {}; | |
| // Client-side dispatch of pointer values. We copy the memory associated with | |
| // the pointer to the server and receive back a server-side pointer to replace | |
| // the client-side pointer in the argument list. | |
| template <uint64_t Idx, typename Tuple, typename CallTuple> | |
| RPC_ATTRS constexpr void prepare_arg(rpc::Client::Port &port, Tuple &t, | |
| CallTuple &ct) { | |
| using ArgTy = rpc::tuple_element_t<Idx, Tuple>; | |
| using CallArgTy = rpc::tuple_element_t<Idx, CallTuple>; | |
| if constexpr (rpc::is_marshalled_ptr_v<ArgTy>) { | |
| // We assume all constant character arrays are C-strings. | |
| uint64_t size{}; | |
| if (rpc::get<Idx>(t)) { | |
| if constexpr (rpc::is_span_v<CallArgTy>) | |
| size = rpc::get<Idx>(ct).size * sizeof(rpc::remove_pointer_t<ArgTy>); | |
| else if constexpr (rpc::is_same_v<ArgTy, const char *>) | |
| size = rpc::string_length(rpc::get<Idx>(t)); | |
| else | |
| size = sizeof(rpc::remove_pointer_t<ArgTy>); | |
| } | |
| port.send_n(rpc::get<Idx>(t), size); | |
| port.recv([&](rpc::Buffer *buffer, uint32_t) { | |
| ArgTy val; | |
| rpc::rpc_memcpy(&val, buffer->data, sizeof(ArgTy)); | |
| rpc::get<Idx>(t) = val; | |
| }); | |
| } | |
| } | |
| // Server-side handling of pointer arguments. We receive the memory into a | |
| // temporary buffer and pass a pointer to this new memory back to the client. | |
| template <uint32_t NUM_LANES, typename Tuple, uint64_t Idx, typename State> | |
| RPC_ATTRS constexpr void prepare_arg(rpc::Server::Port &port, State &&state) { | |
| using ArgTy = rpc::tuple_element_t<Idx, Tuple>; | |
| if constexpr (rpc::is_marshalled_ptr_v<ArgTy>) { | |
| auto &ptrs = state.ptrs[rpc::marshalled_index_v<Tuple, Idx>]; | |
| auto &sizes = state.sizes[rpc::marshalled_index_v<Tuple, Idx>]; | |
| port.recv_n(ptrs, sizes, | |
| [](uint64_t size) { return size ? malloc(size) : 0; }); | |
| port.send([&](rpc::Buffer *buffer, uint32_t id) { | |
| ArgTy val = static_cast<ArgTy>(ptrs[id]); | |
| rpc::rpc_memcpy(buffer->data, &val, sizeof(ArgTy)); | |
| }); | |
| } | |
| } | |
| // Client-side finalization of pointer arguments. If the type is not constant we | |
| // must copy back any potential modifications the invoked function made to that | |
| // pointer. | |
| template <uint64_t Idx, typename Tuple> | |
| RPC_ATTRS constexpr void finish_arg(rpc::Client::Port &port, Tuple &t) { | |
| using ArgTy = rpc::tuple_element_t<Idx, Tuple>; | |
| if constexpr (rpc::is_marshalled_ptr_v<ArgTy> && !rpc::is_const_v<ArgTy>) { | |
| uint64_t size{}; | |
| void *buf{}; | |
| port.recv_n(&buf, &size, [&](uint64_t) { | |
| return const_cast<void *>(static_cast<const void *>(rpc::get<Idx>(t))); | |
| }); | |
| } | |
| } | |
| // Server-side finalization of pointer arguments. We copy any potential | |
| // modifications to the value back to the client unless it was a constant. We | |
| // can also free the associated memory. | |
| template <uint32_t NUM_LANES, typename Tuple, uint64_t Idx, typename State> | |
| RPC_ATTRS constexpr void finish_arg(rpc::Server::Port &port, State &&state) { | |
| using ArgTy = rpc::tuple_element_t<Idx, Tuple>; | |
| if constexpr (rpc::is_marshalled_ptr_v<ArgTy> && !rpc::is_const_v<ArgTy>) { | |
| auto &ptrs = state.ptrs[rpc::marshalled_index_v<Tuple, Idx>]; | |
| auto &sizes = state.sizes[rpc::marshalled_index_v<Tuple, Idx>]; | |
| port.send_n(ptrs, sizes); | |
| } | |
| if constexpr (rpc::is_marshalled_ptr_v<ArgTy>) { | |
| auto &ptrs = state.ptrs[rpc::marshalled_index_v<Tuple, Idx>]; | |
| for (uint32_t id = 0; id < NUM_LANES; ++id) { | |
| if (port.get_lane_mask() & (uint64_t(1) << id)) | |
| free(const_cast<void *>(static_cast<const void *>(ptrs[id]))); | |
| } | |
| } | |
| } | |
| // Iterate over the tuple list of arguments to see if we need to forward any. | |
| // The current forwarding is somewhat inefficient as each pointer is an | |
| // individual RPC call. | |
| template <typename Tuple, typename CallTuple, uint64_t... Is> | |
| RPC_ATTRS constexpr void prepare_args(rpc::Client::Port &port, Tuple &t, | |
| CallTuple &ct, | |
| rpc::index_sequence<Is...>) { | |
| (prepare_arg<Is>(port, t, ct), ...); | |
| } | |
| template <uint32_t NUM_LANES, typename Tuple, typename State, uint64_t... Is> | |
| RPC_ATTRS constexpr void prepare_args(rpc::Server::Port &port, State &&state, | |
| rpc::index_sequence<Is...>) { | |
| (prepare_arg<NUM_LANES, Tuple, Is>(port, state), ...); | |
| } | |
| // Performs the preparation in reverse, copying back any modified values. | |
| template <typename Tuple, uint64_t... Is> | |
| RPC_ATTRS constexpr void finish_args(rpc::Client::Port &port, Tuple &&t, | |
| rpc::index_sequence<Is...>) { | |
| (finish_arg<Is>(port, t), ...); | |
| } | |
| template <uint32_t NUM_LANES, typename Tuple, typename State, uint64_t... Is> | |
| RPC_ATTRS constexpr void finish_args(rpc::Server::Port &port, State &&state, | |
| rpc::index_sequence<Is...>) { | |
| (finish_arg<NUM_LANES, Tuple, Is>(port, state), ...); | |
| } | |
| } // namespace | |
| // Dispatch a function call to the server through the RPC mechanism. Copies the | |
| // argument list through the RPC interface. | |
| template <uint32_t OPCODE, auto Fn, typename... CallArgs> | |
| RPC_ATTRS constexpr typename function_traits<decltype(Fn)>::return_type | |
| dispatch(rpc::Client &client, CallArgs... args) { | |
| using Traits = function_traits<decltype(Fn)>; | |
| using RetTy = typename Traits::return_type; | |
| using TupleTy = typename Traits::arg_types; | |
| using Bytes = tuple_bytes<rpc::remove_span_t<CallArgs>...>; | |
| static_assert(sizeof...(CallArgs) == Traits::ARITY, | |
| "Argument count mismatch"); | |
| static_assert(((rpc::is_trivially_constructible_v<CallArgs> && | |
| rpc::is_trivially_copyable_v<CallArgs>) && | |
| ...), | |
| "Must be a trivial type"); | |
| auto port = client.open<OPCODE>(); | |
| // Copy over any pointer arguments by walking the argument list. | |
| rpc::tuple<CallArgs...> call_args{args...}; | |
| TupleTy arg_tuple{rpc::forward<CallArgs>(args)...}; | |
| rpc::prepare_args(port, arg_tuple, call_args, | |
| rpc::make_index_sequence<Traits::ARITY>{}); | |
| // Compress the argument list to a binary stream and send it to the server. | |
| auto bytes = Bytes::pack(arg_tuple); | |
| port.send_n(&bytes); | |
| // Copy back any potentially modified pointer arguments and the return value. | |
| rpc::finish_args(port, TupleTy{rpc::forward<CallArgs>(args)...}, | |
| rpc::make_index_sequence<Traits::ARITY>{}); | |
| // Copy back the final function return value. | |
| using BufferTy = rpc::conditional_t<rpc::is_void_v<RetTy>, uint8_t, RetTy>; | |
| BufferTy ret{}; | |
| port.recv_n(&ret); | |
| if constexpr (!rpc::is_void_v<RetTy>) | |
| return ret; | |
| } | |
| // Invoke a function on the server on behalf of the client. Receives the | |
| // arguments through the interface and forwards them to the function. | |
| template <uint32_t NUM_LANES, typename FnTy> | |
| RPC_ATTRS constexpr void invoke(rpc::Server::Port &port, FnTy fn) { | |
| using Traits = function_traits<FnTy>; | |
| using RetTy = typename Traits::return_type; | |
| using TupleTy = typename Traits::arg_types; | |
| using Bytes = tuple_bytes<TupleTy>; | |
| // Receive pointer data from the host and pack it in server-side memory. | |
| MarshalledState<NUM_LANES, TupleTy> state{}; | |
| rpc::prepare_args<NUM_LANES, TupleTy>( | |
| port, state, rpc::make_index_sequence<Traits::ARITY>{}); | |
| // Get the argument list from the client. | |
| typename Bytes::array_type arg_buf[NUM_LANES]{}; | |
| port.recv_n(arg_buf); | |
| // Convert the received arguments into an invocable argument list. | |
| TupleTy args[NUM_LANES]{}; | |
| for (uint32_t id = 0; id < NUM_LANES; ++id) { | |
| if (port.get_lane_mask() & (uint64_t(1) << id)) | |
| args[id] = Bytes::unpack(arg_buf[id]); | |
| } | |
| // Execute the function with the provided arguments and send back any copies | |
| // made for pointer data. | |
| using BufferTy = rpc::conditional_t<rpc::is_void_v<RetTy>, uint8_t, RetTy>; | |
| BufferTy rets[NUM_LANES]{}; | |
| for (uint32_t id = 0; id < NUM_LANES; ++id) { | |
| if (port.get_lane_mask() & (uint64_t(1) << id)) { | |
| if constexpr (rpc::is_void_v<RetTy>) | |
| rpc::apply(fn, args[id]); | |
| else | |
| rets[id] = rpc::apply(fn, args[id]); | |
| } | |
| } | |
| // Send any potentially modified pointer arguments back to the client. | |
| rpc::finish_args<NUM_LANES, TupleTy>( | |
| port, state, rpc::make_index_sequence<Traits::ARITY>{}); | |
| // Copy back the return value of the function if one exists. If the function | |
| // is void we send an empty packet to force synchronous behavior. | |
| port.send_n(rets); | |
| } | |
| } // namespace rpc | |
| #endif // LLVM_LIBC_SHARED_RPC_DISPATCH_H |