| //===-- Shared memory RPC server instantiation ------------------*- 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "rpc_server.h" |
| |
| #include "src/__support/RPC/rpc.h" |
| #include <atomic> |
| #include <cstdio> |
| #include <cstring> |
| #include <memory> |
| #include <mutex> |
| #include <unordered_map> |
| #include <variant> |
| #include <vector> |
| |
| using namespace __llvm_libc; |
| |
| static_assert(sizeof(rpc_buffer_t) == sizeof(rpc::Buffer), |
| "Buffer size mismatch"); |
| |
| static_assert(RPC_MAXIMUM_PORT_COUNT == rpc::MAX_PORT_COUNT, |
| "Incorrect maximum port count"); |
| |
| static_assert(RPC_MAXIMUM_LANE_SIZE == rpc::MAX_LANE_SIZE, |
| "Incorrect maximum port count"); |
| |
| // The client needs to support different lane sizes for the SIMT model. Because |
| // of this we need to select between the possible sizes that the client can use. |
| struct Server { |
| template <uint32_t lane_size> |
| Server(std::unique_ptr<rpc::Server<lane_size>> &&server) |
| : server(std::move(server)) {} |
| |
| void reset(uint64_t port_count, void *buffer) { |
| std::visit([&](auto &server) { server->reset(port_count, buffer); }, |
| server); |
| } |
| |
| uint64_t allocation_size(uint64_t port_count) { |
| uint64_t ret = 0; |
| std::visit([&](auto &server) { ret = server->allocation_size(port_count); }, |
| server); |
| return ret; |
| } |
| |
| void *get_buffer_start() const { |
| void *ret = nullptr; |
| std::visit([&](auto &server) { ret = server->get_buffer_start(); }, server); |
| return ret; |
| } |
| |
| rpc_status_t handle_server( |
| std::unordered_map<rpc_opcode_t, rpc_opcode_callback_ty> &callbacks, |
| std::unordered_map<rpc_opcode_t, void *> &callback_data) { |
| rpc_status_t ret = RPC_STATUS_SUCCESS; |
| std::visit( |
| [&](auto &server) { |
| ret = handle_server(*server, callbacks, callback_data); |
| }, |
| server); |
| return ret; |
| } |
| |
| private: |
| template <uint32_t lane_size> |
| rpc_status_t handle_server( |
| rpc::Server<lane_size> &server, |
| std::unordered_map<rpc_opcode_t, rpc_opcode_callback_ty> &callbacks, |
| std::unordered_map<rpc_opcode_t, void *> &callback_data) { |
| auto port = server.try_open(); |
| if (!port) |
| return RPC_STATUS_SUCCESS; |
| |
| switch (port->get_opcode()) { |
| case RPC_WRITE_TO_STREAM: |
| case RPC_WRITE_TO_STDERR: |
| case RPC_WRITE_TO_STDOUT: { |
| uint64_t sizes[rpc::MAX_LANE_SIZE] = {0}; |
| void *strs[rpc::MAX_LANE_SIZE] = {nullptr}; |
| FILE *files[rpc::MAX_LANE_SIZE] = {nullptr}; |
| if (port->get_opcode() == RPC_WRITE_TO_STREAM) |
| port->recv([&](rpc::Buffer *buffer, uint32_t id) { |
| files[id] = reinterpret_cast<FILE *>(buffer->data[0]); |
| }); |
| port->recv_n(strs, sizes, [&](uint64_t size) { return new char[size]; }); |
| port->send([&](rpc::Buffer *buffer, uint32_t id) { |
| FILE *file = |
| port->get_opcode() == RPC_WRITE_TO_STDOUT |
| ? stdout |
| : (port->get_opcode() == RPC_WRITE_TO_STDERR ? stderr |
| : files[id]); |
| uint64_t ret = fwrite(strs[id], 1, sizes[id], file); |
| std::memcpy(buffer->data, &ret, sizeof(uint64_t)); |
| }); |
| for (uint64_t i = 0; i < rpc::MAX_LANE_SIZE; ++i) { |
| if (strs[i]) |
| delete[] reinterpret_cast<uint8_t *>(strs[i]); |
| } |
| break; |
| } |
| case RPC_OPEN_FILE: { |
| uint64_t sizes[rpc::MAX_LANE_SIZE] = {0}; |
| void *paths[rpc::MAX_LANE_SIZE] = {nullptr}; |
| port->recv_n(paths, sizes, [&](uint64_t size) { return new char[size]; }); |
| port->recv_and_send([&](rpc::Buffer *buffer, uint32_t id) { |
| FILE *file = fopen(reinterpret_cast<char *>(paths[id]), |
| reinterpret_cast<char *>(buffer->data)); |
| buffer->data[0] = reinterpret_cast<uintptr_t>(file); |
| }); |
| break; |
| } |
| case RPC_CLOSE_FILE: { |
| port->recv_and_send([&](rpc::Buffer *buffer, uint32_t id) { |
| FILE *file = reinterpret_cast<FILE *>(buffer->data[0]); |
| buffer->data[0] = fclose(file); |
| }); |
| break; |
| } |
| case RPC_EXIT: { |
| // Send a response to the client to signal that we are ready to exit. |
| port->recv_and_send([](rpc::Buffer *) {}); |
| port->recv([](rpc::Buffer *buffer) { |
| int status = 0; |
| std::memcpy(&status, buffer->data, sizeof(int)); |
| exit(status); |
| }); |
| break; |
| } |
| case RPC_HOST_CALL: { |
| uint64_t sizes[rpc::MAX_LANE_SIZE] = {0}; |
| void *args[rpc::MAX_LANE_SIZE] = {nullptr}; |
| port->recv_n(args, sizes, [&](uint64_t size) { return new char[size]; }); |
| port->recv([&](rpc::Buffer *buffer, uint32_t id) { |
| reinterpret_cast<void (*)(void *)>(buffer->data[0])(args[id]); |
| }); |
| port->send([&](rpc::Buffer *, uint32_t id) { |
| delete[] reinterpret_cast<uint8_t *>(args[id]); |
| }); |
| break; |
| } |
| case RPC_NOOP: { |
| port->recv([](rpc::Buffer *) {}); |
| break; |
| } |
| default: { |
| auto handler = |
| callbacks.find(static_cast<rpc_opcode_t>(port->get_opcode())); |
| |
| // We error out on an unhandled opcode. |
| if (handler == callbacks.end()) |
| return RPC_STATUS_UNHANDLED_OPCODE; |
| |
| // Invoke the registered callback with a reference to the port. |
| void *data = |
| callback_data.at(static_cast<rpc_opcode_t>(port->get_opcode())); |
| rpc_port_t port_ref{reinterpret_cast<uint64_t>(&*port), lane_size}; |
| (handler->second)(port_ref, data); |
| } |
| } |
| port->close(); |
| return RPC_STATUS_CONTINUE; |
| } |
| |
| std::variant<std::unique_ptr<rpc::Server<1>>, |
| std::unique_ptr<rpc::Server<32>>, |
| std::unique_ptr<rpc::Server<64>>> |
| server; |
| }; |
| |
| struct Device { |
| template <typename T> |
| Device(std::unique_ptr<T> &&server) : server(std::move(server)) {} |
| Server server; |
| rpc::Client client; |
| std::unordered_map<rpc_opcode_t, rpc_opcode_callback_ty> callbacks; |
| std::unordered_map<rpc_opcode_t, void *> callback_data; |
| }; |
| |
| // A struct containing all the runtime state required to run the RPC server. |
| struct State { |
| State(uint32_t num_devices) |
| : num_devices(num_devices), devices(num_devices), reference_count(0u) {} |
| uint32_t num_devices; |
| std::vector<std::unique_ptr<Device>> devices; |
| std::atomic_uint32_t reference_count; |
| }; |
| |
| static std::mutex startup_mutex; |
| |
| static State *state; |
| |
| rpc_status_t rpc_init(uint32_t num_devices) { |
| std::scoped_lock<decltype(startup_mutex)> lock(startup_mutex); |
| if (!state) |
| state = new State(num_devices); |
| |
| if (state->reference_count == std::numeric_limits<uint32_t>::max()) |
| return RPC_STATUS_ERROR; |
| |
| state->reference_count++; |
| |
| return RPC_STATUS_SUCCESS; |
| } |
| |
| rpc_status_t rpc_shutdown(void) { |
| if (state && state->reference_count-- == 1) |
| delete state; |
| |
| return RPC_STATUS_SUCCESS; |
| } |
| |
| rpc_status_t rpc_server_init(uint32_t device_id, uint64_t num_ports, |
| uint32_t lane_size, rpc_alloc_ty alloc, |
| void *data) { |
| if (!state) |
| return RPC_STATUS_NOT_INITIALIZED; |
| if (device_id >= state->num_devices) |
| return RPC_STATUS_OUT_OF_RANGE; |
| |
| if (!state->devices[device_id]) { |
| switch (lane_size) { |
| case 1: |
| state->devices[device_id] = |
| std::make_unique<Device>(std::make_unique<rpc::Server<1>>()); |
| break; |
| case 32: |
| state->devices[device_id] = |
| std::make_unique<Device>(std::make_unique<rpc::Server<32>>()); |
| break; |
| case 64: |
| state->devices[device_id] = |
| std::make_unique<Device>(std::make_unique<rpc::Server<64>>()); |
| break; |
| default: |
| return RPC_STATUS_INVALID_LANE_SIZE; |
| } |
| } |
| |
| uint64_t size = state->devices[device_id]->server.allocation_size(num_ports); |
| void *buffer = alloc(size, data); |
| |
| if (!buffer) |
| return RPC_STATUS_ERROR; |
| |
| state->devices[device_id]->server.reset(num_ports, buffer); |
| state->devices[device_id]->client.reset(num_ports, buffer); |
| |
| return RPC_STATUS_SUCCESS; |
| } |
| |
| rpc_status_t rpc_server_shutdown(uint32_t device_id, rpc_free_ty dealloc, |
| void *data) { |
| if (!state) |
| return RPC_STATUS_NOT_INITIALIZED; |
| if (device_id >= state->num_devices) |
| return RPC_STATUS_OUT_OF_RANGE; |
| if (!state->devices[device_id]) |
| return RPC_STATUS_ERROR; |
| |
| dealloc(rpc_get_buffer(device_id), data); |
| if (state->devices[device_id]) |
| state->devices[device_id].release(); |
| |
| return RPC_STATUS_SUCCESS; |
| } |
| |
| rpc_status_t rpc_handle_server(uint32_t device_id) { |
| if (!state) |
| return RPC_STATUS_NOT_INITIALIZED; |
| if (device_id >= state->num_devices) |
| return RPC_STATUS_OUT_OF_RANGE; |
| if (!state->devices[device_id]) |
| return RPC_STATUS_ERROR; |
| |
| for (;;) { |
| auto &device = *state->devices[device_id]; |
| rpc_status_t status = |
| device.server.handle_server(device.callbacks, device.callback_data); |
| if (status != RPC_STATUS_CONTINUE) |
| return status; |
| } |
| } |
| |
| rpc_status_t rpc_register_callback(uint32_t device_id, rpc_opcode_t opcode, |
| rpc_opcode_callback_ty callback, |
| void *data) { |
| if (!state) |
| return RPC_STATUS_NOT_INITIALIZED; |
| if (device_id >= state->num_devices) |
| return RPC_STATUS_OUT_OF_RANGE; |
| if (!state->devices[device_id]) |
| return RPC_STATUS_ERROR; |
| |
| state->devices[device_id]->callbacks[opcode] = callback; |
| state->devices[device_id]->callback_data[opcode] = data; |
| return RPC_STATUS_SUCCESS; |
| } |
| |
| void *rpc_get_buffer(uint32_t device_id) { |
| if (!state || device_id >= state->num_devices || !state->devices[device_id]) |
| return nullptr; |
| return state->devices[device_id]->server.get_buffer_start(); |
| } |
| |
| const void *rpc_get_client_buffer(uint32_t device_id) { |
| if (!state || device_id >= state->num_devices || !state->devices[device_id]) |
| return nullptr; |
| return &state->devices[device_id]->client; |
| } |
| |
| uint64_t rpc_get_client_size() { return sizeof(rpc::Client); } |
| |
| using ServerPort = std::variant<rpc::Server<1>::Port *, rpc::Server<32>::Port *, |
| rpc::Server<64>::Port *>; |
| |
| ServerPort get_port(rpc_port_t ref) { |
| if (ref.lane_size == 1) |
| return reinterpret_cast<rpc::Server<1>::Port *>(ref.handle); |
| else if (ref.lane_size == 32) |
| return reinterpret_cast<rpc::Server<32>::Port *>(ref.handle); |
| else if (ref.lane_size == 64) |
| return reinterpret_cast<rpc::Server<64>::Port *>(ref.handle); |
| else |
| __builtin_unreachable(); |
| } |
| |
| void rpc_send(rpc_port_t ref, rpc_port_callback_ty callback, void *data) { |
| auto port = get_port(ref); |
| std::visit( |
| [=](auto &port) { |
| port->send([=](rpc::Buffer *buffer) { |
| callback(reinterpret_cast<rpc_buffer_t *>(buffer), data); |
| }); |
| }, |
| port); |
| } |
| |
| void rpc_send_n(rpc_port_t ref, const void *const *src, uint64_t *size) { |
| auto port = get_port(ref); |
| std::visit([=](auto &port) { port->send_n(src, size); }, port); |
| } |
| |
| void rpc_recv(rpc_port_t ref, rpc_port_callback_ty callback, void *data) { |
| auto port = get_port(ref); |
| std::visit( |
| [=](auto &port) { |
| port->recv([=](rpc::Buffer *buffer) { |
| callback(reinterpret_cast<rpc_buffer_t *>(buffer), data); |
| }); |
| }, |
| port); |
| } |
| |
| void rpc_recv_n(rpc_port_t ref, void **dst, uint64_t *size, rpc_alloc_ty alloc, |
| void *data) { |
| auto port = get_port(ref); |
| auto alloc_fn = [=](uint64_t size) { return alloc(size, data); }; |
| std::visit([=](auto &port) { port->recv_n(dst, size, alloc_fn); }, port); |
| } |
| |
| void rpc_recv_and_send(rpc_port_t ref, rpc_port_callback_ty callback, |
| void *data) { |
| auto port = get_port(ref); |
| std::visit( |
| [=](auto &port) { |
| port->recv_and_send([=](rpc::Buffer *buffer) { |
| callback(reinterpret_cast<rpc_buffer_t *>(buffer), data); |
| }); |
| }, |
| port); |
| } |