//===-- 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 struct tuple_bytes { static constexpr uint64_t SIZE = rpc::max(1ul, (0 + ... + sizeof(Ts))); using array_type = rpc::array; template RPC_ATTRS static constexpr array_type pack_impl(rpc::tuple t, rpc::index_sequence) { array_type out{}; uint8_t *p = out.data(); ((rpc::rpc_memcpy(p, &rpc::get(t), sizeof(Ts)), p += sizeof(Ts)), ...); return out; } RPC_ATTRS static constexpr array_type pack(rpc::tuple t) { return pack_impl(t, rpc::index_sequence_for{}); } template RPC_ATTRS static constexpr rpc::tuple unpack_impl(const uint8_t *data, rpc::index_sequence) { rpc::tuple t{}; const uint8_t *p = data; ((rpc::rpc_memcpy(&rpc::get(t), p, sizeof(Ts)), p += sizeof(Ts)), ...); return t; } RPC_ATTRS static constexpr rpc::tuple unpack(const array_type &a) { return unpack_impl(a.data(), rpc::index_sequence_for{}); } }; template struct tuple_bytes> : tuple_bytes {}; // 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 RPC_ATTRS constexpr void prepare_arg(rpc::Client::Port &port, Tuple &t) { using ArgTy = rpc::tuple_element_t; using ElemTy = rpc::remove_pointer_t; if constexpr (rpc::is_pointer_v && rpc::is_complete_v && !rpc::is_void_v) { // We assume all constant character arrays are C-strings. uint64_t size{}; if constexpr (rpc::is_same_v) size = rpc::string_length(rpc::get(t)); else size = sizeof(rpc::remove_pointer_t); port.send_n(rpc::get(t), size); port.recv([&](rpc::Buffer *buffer, uint32_t) { rpc::get(t) = *reinterpret_cast(buffer->data); }); } } // 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 RPC_ATTRS constexpr void prepare_arg(rpc::Server::Port &port) { using ArgTy = rpc::tuple_element_t; using ElemTy = rpc::remove_pointer_t; if constexpr (rpc::is_pointer_v && rpc::is_complete_v && !rpc::is_void_v) { void *args[NUM_LANES]{}; uint64_t sizes[NUM_LANES]{}; port.recv_n(args, sizes, [](uint64_t size) { if constexpr (rpc::is_same_v) return malloc(size); else return malloc( sizeof(rpc::remove_const_t>)); }); port.send([&](rpc::Buffer *buffer, uint32_t id) { *reinterpret_cast(buffer->data) = static_cast(args[id]); }); } } // 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 RPC_ATTRS constexpr void finish_arg(rpc::Client::Port &port, Tuple &t) { using ArgTy = rpc::tuple_element_t; using ElemTy = rpc::remove_pointer_t; using MemoryTy = rpc::remove_const_t> *; if constexpr (rpc::is_pointer_v && !rpc::is_const_v && rpc::is_complete_v && !rpc::is_void_v) { uint64_t size{}; void *buf{}; port.recv_n(&buf, &size, [&](uint64_t) { return const_cast(rpc::get(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 RPC_ATTRS constexpr void finish_arg(rpc::Server::Port &port, Tuple (&t)[NUM_LANES]) { using ArgTy = rpc::tuple_element_t; using ElemTy = rpc::remove_pointer_t; if constexpr (rpc::is_pointer_v && !rpc::is_const_v && rpc::is_complete_v && !rpc::is_void_v) { const void *buffer[NUM_LANES]{}; size_t sizes[NUM_LANES]{}; for (uint32_t id = 0; id < NUM_LANES; ++id) { if (port.get_lane_mask() & (uint64_t(1) << id)) { buffer[id] = rpc::get(t[id]); sizes[id] = sizeof(rpc::remove_pointer_t); } } port.send_n(buffer, sizes); } if constexpr (rpc::is_pointer_v && rpc::is_complete_v && !rpc::is_void_v) { for (uint32_t id = 0; id < NUM_LANES; ++id) { if (port.get_lane_mask() & (uint64_t(1) << id)) free(const_cast( static_cast(rpc::get(t[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 RPC_ATTRS constexpr void prepare_args(rpc::Client::Port &port, Tuple &t, rpc::index_sequence) { (prepare_arg(port, t), ...); } template RPC_ATTRS constexpr void prepare_args(rpc::Server::Port &port, rpc::index_sequence) { (prepare_arg(port), ...); } // Performs the preparation in reverse, copying back any modified values. template RPC_ATTRS constexpr void finish_args(rpc::Client::Port &port, Tuple &&t, rpc::index_sequence) { (finish_arg(port, t), ...); } template RPC_ATTRS constexpr void finish_args(rpc::Server::Port &port, Tuple (&t)[NUM_LANES], rpc::index_sequence) { (finish_arg(port, t), ...); } } // namespace // Dispatch a function call to the server through the RPC mechanism. Copies the // argument list through the RPC interface. template RPC_ATTRS constexpr typename function_traits::return_type dispatch(rpc::Client &client, FnTy, CallArgs... args) { using Traits = function_traits; using RetTy = typename Traits::return_type; using TupleTy = typename Traits::arg_types; using Bytes = tuple_bytes; static_assert(sizeof...(CallArgs) == Traits::ARITY, "Argument count mismatch"); static_assert(((rpc::is_trivially_constructible_v && rpc::is_trivially_copyable_v) && ...), "Must be a trivial type"); auto port = client.open(); // Copy over any pointer arguments by walking the argument list. TupleTy arg_tuple{rpc::forward(args)...}; rpc::prepare_args(port, arg_tuple, rpc::make_index_sequence{}); // 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(args)...}, rpc::make_index_sequence{}); // Copy back the final function return value. using BufferTy = rpc::conditional_t, uint8_t, RetTy>; BufferTy ret{}; port.recv_n(&ret); if constexpr (!rpc::is_void_v) 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 RPC_ATTRS constexpr void invoke(rpc::Server::Port &port, FnTy fn) { using Traits = function_traits; using RetTy = typename Traits::return_type; using TupleTy = typename Traits::arg_types; using Bytes = tuple_bytes; // Receive pointer data from the host and pack it in server-side memory. rpc::prepare_args( port, rpc::make_index_sequence{}); // 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, 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) 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(port, args, rpc::make_index_sequence{}); // 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