| //===-- 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #ifndef LLVM_LIBC_SHARED_RPC_SERVER_H | |
| #define LLVM_LIBC_SHARED_RPC_SERVER_H | |
| #include "rpc.h" | |
| #include "rpc_opcodes.h" | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #ifdef _WIN32 | |
| #define flockfile _lock_file | |
| #define funlockfile _unlock_file | |
| #define fwrite_unlocked _fwrite_nolock | |
| #elif defined(__APPLE__) | |
| // MacOS doesn't have an equivalent of fwrite_unlocked so we just use | |
| // fwrite. | |
| #define fwrite_unlocked fwrite | |
| #endif | |
| namespace rpc { | |
| namespace internal { | |
| // Minimal replacement for 'std::vector' that works for trivial types. | |
| template <typename T> class TempVector { | |
| T *data_ = nullptr; | |
| size_t current = 0; | |
| size_t capacity = 0; | |
| public: | |
| ~TempVector() { ::free(data_); } | |
| void push_back(const T &value) { | |
| if (current == capacity) | |
| grow(); | |
| data_[current++] = value; | |
| } | |
| void pop_back() { --current; } | |
| bool empty() const { return current == 0; } | |
| size_t size() const { return current; } | |
| T &operator[](size_t index) { return data_[index]; } | |
| T &back() { return data_[current - 1]; } | |
| T *begin() const { return data_; } | |
| T *end() const { return data_ + current; } | |
| private: | |
| void grow() { | |
| size_t new_capacity = capacity ? capacity * 2 : 1; | |
| void *new_data = ::realloc(data_, new_capacity * sizeof(T)); | |
| data_ = static_cast<T *>(new_data); | |
| capacity = new_capacity; | |
| } | |
| }; | |
| struct TempStorage { | |
| char *alloc(size_t size) { | |
| storage.push_back(reinterpret_cast<char *>(::malloc(size))); | |
| return storage.back(); | |
| } | |
| ~TempStorage() { | |
| for (char *ptr : storage) | |
| ::free(ptr); | |
| } | |
| TempVector<char *> storage; | |
| }; | |
| // Counts the bytes consumed from a variadic argument list without reading data. | |
| template <bool packed> struct DummyArgList { | |
| size_t count = 0; | |
| template <class T> inline T next_var() { | |
| count = | |
| packed ? count + sizeof(T) : align_up(count, alignof(T)) + sizeof(T); | |
| return T(count); | |
| } | |
| size_t read_count() const { return count; } | |
| }; | |
| // Reads variadic arguments from a pre-built byte buffer. | |
| template <bool packed> struct StructArgList { | |
| void *ptr; | |
| void *end; | |
| StructArgList() = default; | |
| inline StructArgList(void *ptr, size_t size) | |
| : ptr(ptr), end(static_cast<unsigned char *>(ptr) + size) {} | |
| template <class T> inline T next_var() { | |
| if (!packed) | |
| ptr = reinterpret_cast<void *>( | |
| align_up(reinterpret_cast<uintptr_t>(ptr), alignof(T))); | |
| if (ptr >= end) | |
| return T(-1); | |
| T val; | |
| ::memcpy(&val, ptr, sizeof(T)); | |
| ptr = | |
| reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(ptr) + sizeof(T)); | |
| return val; | |
| } | |
| }; | |
| // Get the associated stream out of an encoded number. | |
| inline FILE *to_stream(uintptr_t f) { | |
| enum Stream { File = 0, Stdin = 1, Stdout = 2, Stderr = 3 }; | |
| FILE *stream = reinterpret_cast<FILE *>(f & ~0x3ull); | |
| Stream type = static_cast<Stream>(f & 0x3ull); | |
| if (type == Stdin) | |
| return stdin; | |
| if (type == Stdout) | |
| return stdout; | |
| if (type == Stderr) | |
| return stderr; | |
| return stream; | |
| } | |
| inline constexpr bool is_format_flag(char c) { | |
| return c == ' ' || c == '-' || c == '+' || c == '#' || c == '0'; | |
| } | |
| inline constexpr bool is_digit(char c) { return c >= '0' && c <= '9'; } | |
| enum class LengthModifier { none, l }; | |
| enum class SizeArgument { finished, width, precision }; | |
| struct Specifier { | |
| uintptr_t raw_value = 0; | |
| char conv_name = '\0'; | |
| bool is_string = false; | |
| bool is_finished = false; | |
| bool is_star = false; | |
| bool is_long = false; | |
| }; | |
| // Minimal printf format string parser. Walks the format and extracts the type | |
| // and size of each variadic argument consumed by a conversion specifier. | |
| template <typename ArgProvider> struct MicroParser { | |
| inline MicroParser(const char *format, ArgProvider args) | |
| : format(format), args(args) {} | |
| inline uint32_t pos() const { return cur_pos; } | |
| inline uint32_t spec_start() const { return spec_begin; } | |
| inline Specifier get_next_specifier() { | |
| Specifier specifier{}; | |
| while (format[cur_pos] != '\0' && format[cur_pos] != '%' && | |
| size_pos == SizeArgument::finished) | |
| ++cur_pos; | |
| if (format[cur_pos] == '\0') { | |
| specifier.is_finished = true; | |
| return specifier; | |
| } | |
| if (size_pos == SizeArgument::finished) | |
| spec_begin = cur_pos; | |
| cur_pos++; | |
| if (size_pos == SizeArgument::finished) { | |
| while (format[cur_pos] != '\0' && is_format_flag(format[cur_pos])) | |
| ++cur_pos; | |
| if (format[cur_pos] == '*') { | |
| specifier.raw_value = | |
| static_cast<uintptr_t>(args.template next_var<uint32_t>()); | |
| specifier.is_star = true; | |
| size_pos = SizeArgument::width; | |
| return specifier; | |
| } | |
| while (format[cur_pos] != '\0' && is_digit(format[cur_pos])) | |
| ++cur_pos; | |
| } | |
| if (format[cur_pos] == '.' && size_pos != SizeArgument::precision) { | |
| ++cur_pos; | |
| if (format[cur_pos] == '*') { | |
| specifier.raw_value = | |
| static_cast<uintptr_t>(args.template next_var<uint32_t>()); | |
| specifier.is_star = true; | |
| size_pos = SizeArgument::precision; | |
| return specifier; | |
| } | |
| while (format[cur_pos] != '\0' && is_digit(format[cur_pos])) | |
| ++cur_pos; | |
| } | |
| LengthModifier lm = parse_length_modifier(); | |
| specifier.is_long = lm == LengthModifier::l; | |
| specifier.conv_name = format[cur_pos]; | |
| switch (format[cur_pos]) { | |
| case 'c': | |
| specifier.raw_value = | |
| static_cast<uintptr_t>(args.template next_var<uint32_t>()); | |
| break; | |
| case 'd': | |
| case 'b': | |
| case 'B': | |
| case 'i': | |
| case 'o': | |
| case 'x': | |
| case 'X': | |
| case 'u': | |
| if (lm == LengthModifier::none) | |
| specifier.raw_value = | |
| static_cast<uintptr_t>(args.template next_var<uint32_t>()); | |
| else | |
| specifier.raw_value = | |
| static_cast<uintptr_t>(args.template next_var<uint64_t>()); | |
| break; | |
| case 'f': | |
| case 'F': | |
| case 'e': | |
| case 'E': | |
| case 'a': | |
| case 'A': | |
| case 'g': | |
| case 'G': { | |
| double d = args.template next_var<double>(); | |
| ::memcpy(&specifier.raw_value, &d, sizeof(double)); | |
| break; | |
| } | |
| case 'p': | |
| specifier.raw_value = | |
| reinterpret_cast<uintptr_t>(args.template next_var<void *>()); | |
| break; | |
| case 's': | |
| specifier.raw_value = | |
| reinterpret_cast<uintptr_t>(args.template next_var<void *>()); | |
| specifier.is_string = true; | |
| break; | |
| case 'n': | |
| specifier.raw_value = | |
| reinterpret_cast<uintptr_t>(args.template next_var<void *>()); | |
| break; | |
| case '%': | |
| break; | |
| default: | |
| if (format[cur_pos] == '\0') { | |
| specifier.is_finished = true; | |
| return specifier; | |
| } | |
| break; | |
| } | |
| cur_pos++; | |
| size_pos = SizeArgument::finished; | |
| return specifier; | |
| } | |
| private: | |
| inline LengthModifier parse_length_modifier() { | |
| switch (format[cur_pos]) { | |
| case 'l': | |
| if (format[cur_pos + 1] == 'l') | |
| ++cur_pos; | |
| [[fallthrough]]; | |
| case 't': | |
| case 'j': | |
| case 'z': | |
| ++cur_pos; | |
| return LengthModifier::l; | |
| case 'h': | |
| if (format[cur_pos + 1] == 'h') | |
| ++cur_pos; | |
| ++cur_pos; | |
| return LengthModifier::none; | |
| case 'q': | |
| case 'L': | |
| ++cur_pos; | |
| return LengthModifier::l; | |
| default: | |
| return LengthModifier::none; | |
| } | |
| } | |
| const char *const format; | |
| ArgProvider args; | |
| uint32_t cur_pos = 0; | |
| uint32_t spec_begin = 0; | |
| SizeArgument size_pos = SizeArgument::finished; | |
| }; | |
| // The format strings were already checked and warned on the device side. | |
| #pragma GCC diagnostic push | |
| #pragma GCC diagnostic ignored "-Wformat-nonliteral" | |
| #pragma GCC diagnostic ignored "-Wmissing-format-attribute" | |
| // Dispatch helper that passes dynamic '*' width/precision values to fprintf. | |
| template <typename T> | |
| [[gnu::format(printf, 2, 0)]] inline int | |
| fprintf_with_stars(FILE *file, const char *fmt, int num_stars, int *star_vals, | |
| T val) { | |
| if (num_stars == 2) | |
| return ::fprintf(file, fmt, star_vals[0], star_vals[1], val); | |
| if (num_stars == 1) | |
| return ::fprintf(file, fmt, star_vals[0], val); | |
| return ::fprintf(file, fmt, val); | |
| } | |
| // Walks a printf format string using the MicroParser and emits output in | |
| // chunks via fprintf. Literal text is written directly with fwrite_unlocked. | |
| // The caller must hold the stream lock via flockfile. | |
| template <bool packed> | |
| inline int print_format(FILE *file, const char *fmt, StructArgList<packed> args, | |
| TempVector<void *> &copied_strs) { | |
| MicroParser<StructArgList<packed>> parser(fmt, args); | |
| int ret = 0; | |
| size_t prev = 0; | |
| int star_vals[2]; | |
| int num_stars = 0; | |
| // Accumulate any errors so we correctly return on failure. | |
| auto accum = [&](int rc) { | |
| if (ret >= 0) | |
| ret = (rc < 0) ? rc : ret + rc; | |
| }; | |
| auto write = [&](const char *s, size_t n) -> int { | |
| size_t w = ::fwrite_unlocked(s, 1, n, file); | |
| return w == n ? static_cast<int>(w) : -1; | |
| }; | |
| for (Specifier spec = parser.get_next_specifier(); !spec.is_finished; | |
| spec = parser.get_next_specifier()) { | |
| if (spec.is_star) { | |
| if (num_stars < 2) | |
| star_vals[num_stars++] = static_cast<int>(spec.raw_value); | |
| continue; | |
| } | |
| size_t start = parser.spec_start(); | |
| size_t end = parser.pos(); | |
| if (start > prev) | |
| accum(write(fmt + prev, start - prev)); | |
| // Null-terminated copy of the specifier substring for fprintf. Use a | |
| // stack buffer for the common case; heap-allocate only for overlong specs. | |
| size_t len = end - start; | |
| char local_buf[32]; | |
| char *buf = len < sizeof(local_buf) ? local_buf : new char[len + 1]; | |
| ::memcpy(buf, fmt + start, len); | |
| buf[len] = '\0'; | |
| switch (spec.conv_name) { | |
| case 's': { | |
| const char *str = reinterpret_cast<const char *>(spec.raw_value); | |
| if (str) { | |
| str = reinterpret_cast<const char *>(copied_strs.back()); | |
| copied_strs.pop_back(); | |
| } | |
| accum(fprintf_with_stars(file, buf, num_stars, star_vals, str)); | |
| break; | |
| } | |
| case 'n': | |
| break; | |
| case 'p': | |
| accum(fprintf_with_stars(file, buf, num_stars, star_vals, | |
| reinterpret_cast<void *>(spec.raw_value))); | |
| break; | |
| case 'f': | |
| case 'F': | |
| case 'e': | |
| case 'E': | |
| case 'a': | |
| case 'A': | |
| case 'g': | |
| case 'G': { | |
| double d; | |
| ::memcpy(&d, &spec.raw_value, sizeof(double)); | |
| if (spec.is_long) | |
| accum(fprintf_with_stars(file, buf, num_stars, star_vals, | |
| static_cast<long double>(d))); | |
| else | |
| accum(fprintf_with_stars(file, buf, num_stars, star_vals, d)); | |
| break; | |
| } | |
| default: | |
| if (spec.is_long) | |
| accum(fprintf_with_stars(file, buf, num_stars, star_vals, | |
| spec.raw_value)); | |
| else | |
| accum(fprintf_with_stars(file, buf, num_stars, star_vals, | |
| static_cast<uint32_t>(spec.raw_value))); | |
| break; | |
| } | |
| if (buf != local_buf) | |
| delete[] buf; | |
| num_stars = 0; | |
| prev = end; | |
| } | |
| if (parser.pos() > prev) | |
| accum(write(fmt + prev, parser.pos() - prev)); | |
| return ret; | |
| } | |
| #pragma GCC diagnostic pop | |
| template <bool packed, uint32_t num_lanes> | |
| inline void handle_printf(Server::Port &port, TempStorage &temp_storage) { | |
| FILE *files[num_lanes] = {nullptr}; | |
| // Get the appropriate output stream to use. | |
| if (port.get_opcode() == LIBC_PRINTF_TO_STREAM || | |
| port.get_opcode() == LIBC_PRINTF_TO_STREAM_PACKED) { | |
| port.recv([&](Buffer *buffer, uint32_t id) { | |
| files[id] = reinterpret_cast<FILE *>(buffer->data[0]); | |
| }); | |
| } else if (port.get_opcode() == LIBC_PRINTF_TO_STDOUT || | |
| port.get_opcode() == LIBC_PRINTF_TO_STDOUT_PACKED) { | |
| for (uint32_t i = 0; i < num_lanes; ++i) | |
| files[i] = stdout; | |
| } else { | |
| for (uint32_t i = 0; i < num_lanes; ++i) | |
| files[i] = stderr; | |
| } | |
| uint64_t format_sizes[num_lanes] = {0}; | |
| void *format[num_lanes] = {nullptr}; | |
| uint64_t args_sizes[num_lanes] = {0}; | |
| void *args[num_lanes] = {nullptr}; | |
| // Receive the format string from the client. | |
| port.recv_n(format, format_sizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| // Parse the format string to determine the expected argument buffer size. | |
| for (uint32_t lane = 0; lane < num_lanes; ++lane) { | |
| if (!format[lane]) | |
| continue; | |
| DummyArgList<packed> dummy_args; | |
| MicroParser<DummyArgList<packed> &> parser( | |
| reinterpret_cast<const char *>(format[lane]), dummy_args); | |
| for (Specifier spec = parser.get_next_specifier(); !spec.is_finished; | |
| spec = parser.get_next_specifier()) | |
| ; | |
| args_sizes[lane] = dummy_args.read_count(); | |
| } | |
| port.send( | |
| [&](Buffer *buffer, uint32_t id) { buffer->data[0] = args_sizes[id]; }); | |
| port.recv_n(args, args_sizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| // Identify any arguments that are actually pointers to strings on the client. | |
| TempVector<void *> strs_to_copy[num_lanes]; | |
| for (uint32_t lane = 0; lane < num_lanes; ++lane) { | |
| if (!format[lane]) | |
| continue; | |
| StructArgList<packed> struct_args(args[lane], args_sizes[lane]); | |
| MicroParser<StructArgList<packed>> parser( | |
| reinterpret_cast<const char *>(format[lane]), struct_args); | |
| for (Specifier spec = parser.get_next_specifier(); !spec.is_finished; | |
| spec = parser.get_next_specifier()) { | |
| if (spec.is_string && spec.raw_value) | |
| strs_to_copy[lane].push_back(reinterpret_cast<void *>(spec.raw_value)); | |
| } | |
| } | |
| // Receive any strings from the client and push them into a buffer. | |
| TempVector<void *> copied_strs[num_lanes]; | |
| auto has_pending = [](TempVector<void *> v[num_lanes]) { | |
| for (uint32_t i = 0; i < num_lanes; ++i) | |
| if (!v[i].empty() && v[i].back()) | |
| return true; | |
| return false; | |
| }; | |
| while (has_pending(strs_to_copy)) { | |
| port.send([&](Buffer *buffer, uint32_t id) { | |
| void *ptr = !strs_to_copy[id].empty() ? strs_to_copy[id].back() : nullptr; | |
| buffer->data[1] = reinterpret_cast<uintptr_t>(ptr); | |
| if (!strs_to_copy[id].empty()) | |
| strs_to_copy[id].pop_back(); | |
| }); | |
| uint64_t str_sizes[num_lanes] = {0}; | |
| void *strs[num_lanes] = {nullptr}; | |
| port.recv_n(strs, str_sizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| for (uint32_t lane = 0; lane < num_lanes; ++lane) { | |
| if (!strs[lane]) | |
| continue; | |
| copied_strs[lane].push_back(strs[lane]); | |
| } | |
| } | |
| // Print using a locked stream, emitting each format chunk via fprintf. | |
| int results[num_lanes] = {0}; | |
| for (uint32_t lane = 0; lane < num_lanes; ++lane) { | |
| if (!format[lane]) | |
| continue; | |
| StructArgList<packed> printf_args(args[lane], args_sizes[lane]); | |
| ::flockfile(files[lane]); | |
| results[lane] = print_format<packed>( | |
| files[lane], reinterpret_cast<const char *>(format[lane]), printf_args, | |
| copied_strs[lane]); | |
| ::funlockfile(files[lane]); | |
| } | |
| // Send the final return value and signal completion by setting the string | |
| // argument to null. | |
| port.send([&](Buffer *buffer, uint32_t id) { | |
| buffer->data[0] = static_cast<uint64_t>(results[id]); | |
| buffer->data[1] = reinterpret_cast<uintptr_t>(nullptr); | |
| }); | |
| } | |
| template <uint32_t num_lanes> | |
| inline RPCStatus handle_port_impl(Server::Port &port) { | |
| TempStorage temp_storage; | |
| switch (port.get_opcode()) { | |
| case LIBC_WRITE_TO_STREAM: | |
| case LIBC_WRITE_TO_STDERR: | |
| case LIBC_WRITE_TO_STDOUT: | |
| case LIBC_WRITE_TO_STDOUT_NEWLINE: { | |
| uint64_t sizes[num_lanes] = {0}; | |
| void *strs[num_lanes] = {nullptr}; | |
| FILE *files[num_lanes] = {nullptr}; | |
| if (port.get_opcode() == LIBC_WRITE_TO_STREAM) { | |
| port.recv([&](Buffer *buffer, uint32_t id) { | |
| files[id] = reinterpret_cast<FILE *>(buffer->data[0]); | |
| }); | |
| } else { | |
| for (uint32_t i = 0; i < num_lanes; ++i) | |
| files[i] = port.get_opcode() == LIBC_WRITE_TO_STDERR ? stderr : stdout; | |
| } | |
| port.recv_n(strs, sizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| port.send([&](Buffer *buffer, uint32_t id) { | |
| ::flockfile(files[id]); | |
| buffer->data[0] = ::fwrite_unlocked(strs[id], 1, sizes[id], files[id]); | |
| if (port.get_opcode() == LIBC_WRITE_TO_STDOUT_NEWLINE && | |
| buffer->data[0] == sizes[id]) | |
| buffer->data[0] += ::fwrite_unlocked("\n", 1, 1, files[id]); | |
| ::funlockfile(files[id]); | |
| }); | |
| break; | |
| } | |
| case LIBC_READ_FROM_STREAM: { | |
| uint64_t sizes[num_lanes] = {0}; | |
| void *data[num_lanes] = {nullptr}; | |
| port.recv([&](Buffer *buffer, uint32_t id) { | |
| data[id] = temp_storage.alloc(buffer->data[0]); | |
| sizes[id] = | |
| ::fread(data[id], 1, buffer->data[0], to_stream(buffer->data[1])); | |
| }); | |
| port.send_n(data, sizes); | |
| port.send([&](Buffer *buffer, uint32_t id) { | |
| ::memcpy(buffer->data, &sizes[id], sizeof(uint64_t)); | |
| }); | |
| break; | |
| } | |
| case LIBC_READ_FGETS: { | |
| uint64_t sizes[num_lanes] = {0}; | |
| void *data[num_lanes] = {nullptr}; | |
| port.recv([&](Buffer *buffer, uint32_t id) { | |
| data[id] = temp_storage.alloc(buffer->data[0]); | |
| const char *str = ::fgets(reinterpret_cast<char *>(data[id]), | |
| static_cast<int>(buffer->data[0]), | |
| to_stream(buffer->data[1])); | |
| sizes[id] = !str ? 0 : __builtin_strlen(str) + 1; | |
| }); | |
| port.send_n(data, sizes); | |
| break; | |
| } | |
| case LIBC_OPEN_FILE: { | |
| uint64_t sizes[num_lanes] = {0}; | |
| void *paths[num_lanes] = {nullptr}; | |
| port.recv_n(paths, sizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| port.recv_and_send([&](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 LIBC_CLOSE_FILE: { | |
| port.recv_and_send([&](Buffer *buffer, uint32_t) { | |
| FILE *file = reinterpret_cast<FILE *>(buffer->data[0]); | |
| buffer->data[0] = static_cast<uint64_t>(::fclose(file)); | |
| }); | |
| break; | |
| } | |
| case LIBC_EXIT: { | |
| port.recv_and_send([](Buffer *, uint32_t) {}); | |
| port.recv([](Buffer *buffer, uint32_t) { | |
| int status = 0; | |
| ::memcpy(&status, buffer->data, sizeof(int)); | |
| ::quick_exit(status); | |
| }); | |
| break; | |
| } | |
| case LIBC_ABORT: { | |
| port.recv_and_send([](Buffer *, uint32_t) {}); | |
| port.recv([](Buffer *, uint32_t) {}); | |
| ::abort(); | |
| break; | |
| } | |
| case LIBC_HOST_CALL: { | |
| uint64_t sizes[num_lanes] = {0}; | |
| unsigned long long results[num_lanes] = {0}; | |
| void *args[num_lanes] = {nullptr}; | |
| port.recv_n(args, sizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| port.recv([&](Buffer *buffer, uint32_t id) { | |
| using func_ptr_t = unsigned long long (*)(void *); | |
| auto func = reinterpret_cast<func_ptr_t>(buffer->data[0]); | |
| results[id] = func(args[id]); | |
| }); | |
| port.send([&](Buffer *buffer, uint32_t id) { | |
| buffer->data[0] = static_cast<uint64_t>(results[id]); | |
| }); | |
| break; | |
| } | |
| case LIBC_FEOF: { | |
| port.recv_and_send([](Buffer *buffer, uint32_t) { | |
| buffer->data[0] = | |
| static_cast<uint64_t>(::feof(to_stream(buffer->data[0]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_FERROR: { | |
| port.recv_and_send([](Buffer *buffer, uint32_t) { | |
| buffer->data[0] = | |
| static_cast<uint64_t>(::ferror(to_stream(buffer->data[0]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_CLEARERR: { | |
| port.recv_and_send([](Buffer *buffer, uint32_t) { | |
| ::clearerr(to_stream(buffer->data[0])); | |
| }); | |
| break; | |
| } | |
| case LIBC_FSEEK: { | |
| port.recv_and_send([](Buffer *buffer, uint32_t) { | |
| buffer->data[0] = static_cast<uint64_t>(::fseek( | |
| to_stream(buffer->data[0]), static_cast<long>(buffer->data[1]), | |
| static_cast<int>(buffer->data[2]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_FTELL: { | |
| port.recv_and_send([](Buffer *buffer, uint32_t) { | |
| buffer->data[0] = | |
| static_cast<uint64_t>(::ftell(to_stream(buffer->data[0]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_FFLUSH: { | |
| port.recv_and_send([](Buffer *buffer, uint32_t) { | |
| buffer->data[0] = | |
| static_cast<uint64_t>(::fflush(to_stream(buffer->data[0]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_UNGETC: { | |
| port.recv_and_send([](Buffer *buffer, uint32_t) { | |
| buffer->data[0] = static_cast<uint64_t>(::ungetc( | |
| static_cast<int>(buffer->data[0]), to_stream(buffer->data[1]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_PRINTF_TO_STREAM_PACKED: | |
| case LIBC_PRINTF_TO_STDOUT_PACKED: | |
| case LIBC_PRINTF_TO_STDERR_PACKED: { | |
| handle_printf<true, num_lanes>(port, temp_storage); | |
| break; | |
| } | |
| case LIBC_PRINTF_TO_STREAM: | |
| case LIBC_PRINTF_TO_STDOUT: | |
| case LIBC_PRINTF_TO_STDERR: { | |
| handle_printf<false, num_lanes>(port, temp_storage); | |
| break; | |
| } | |
| case LIBC_REMOVE: { | |
| uint64_t sizes[num_lanes] = {0}; | |
| void *args[num_lanes] = {nullptr}; | |
| port.recv_n(args, sizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| port.send([&](Buffer *buffer, uint32_t id) { | |
| buffer->data[0] = static_cast<uint64_t>( | |
| ::remove(reinterpret_cast<const char *>(args[id]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_RENAME: { | |
| uint64_t oldsizes[num_lanes] = {0}; | |
| uint64_t newsizes[num_lanes] = {0}; | |
| void *oldpath[num_lanes] = {nullptr}; | |
| void *newpath[num_lanes] = {nullptr}; | |
| port.recv_n(oldpath, oldsizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| port.recv_n(newpath, newsizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| port.send([&](Buffer *buffer, uint32_t id) { | |
| buffer->data[0] = static_cast<uint64_t>( | |
| ::rename(reinterpret_cast<const char *>(oldpath[id]), | |
| reinterpret_cast<const char *>(newpath[id]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_SYSTEM: { | |
| uint64_t sizes[num_lanes] = {0}; | |
| void *args[num_lanes] = {nullptr}; | |
| port.recv_n(args, sizes, | |
| [&](uint64_t size) { return temp_storage.alloc(size); }); | |
| port.send([&](Buffer *buffer, uint32_t id) { | |
| buffer->data[0] = static_cast<uint64_t>( | |
| ::system(reinterpret_cast<const char *>(args[id]))); | |
| }); | |
| break; | |
| } | |
| case LIBC_TEST_INCREMENT: { | |
| port.recv_and_send([](Buffer *buffer, uint32_t) { | |
| reinterpret_cast<uint64_t *>(buffer->data)[0] += 1; | |
| }); | |
| break; | |
| } | |
| case LIBC_TEST_INTERFACE: { | |
| bool end_with_recv; | |
| uint64_t cnt; | |
| port.recv( | |
| [&](Buffer *buffer, uint32_t) { end_with_recv = buffer->data[0]; }); | |
| port.recv([&](Buffer *buffer, uint32_t) { cnt = buffer->data[0]; }); | |
| port.send( | |
| [&](Buffer *buffer, uint32_t) { buffer->data[0] = cnt = cnt + 1; }); | |
| port.recv([&](Buffer *buffer, uint32_t) { cnt = buffer->data[0]; }); | |
| port.send( | |
| [&](Buffer *buffer, uint32_t) { buffer->data[0] = cnt = cnt + 1; }); | |
| port.recv([&](Buffer *buffer, uint32_t) { cnt = buffer->data[0]; }); | |
| port.recv([&](Buffer *buffer, uint32_t) { cnt = buffer->data[0]; }); | |
| port.send( | |
| [&](Buffer *buffer, uint32_t) { buffer->data[0] = cnt = cnt + 1; }); | |
| port.send( | |
| [&](Buffer *buffer, uint32_t) { buffer->data[0] = cnt = cnt + 1; }); | |
| if (end_with_recv) | |
| port.recv([&](Buffer *buffer, uint32_t) { cnt = buffer->data[0]; }); | |
| else | |
| port.send( | |
| [&](Buffer *buffer, uint32_t) { buffer->data[0] = cnt = cnt + 1; }); | |
| break; | |
| } | |
| case LIBC_TEST_STREAM: { | |
| uint64_t sizes[num_lanes] = {0}; | |
| void *dst[num_lanes] = {nullptr}; | |
| port.recv_n(dst, sizes, | |
| [](uint64_t size) -> void * { return new char[size]; }); | |
| port.send_n(dst, sizes); | |
| for (uint64_t i = 0; i < num_lanes; ++i) { | |
| if (dst[i]) | |
| delete[] reinterpret_cast<uint8_t *>(dst[i]); | |
| } | |
| break; | |
| } | |
| case LIBC_NOOP: { | |
| port.recv([](Buffer *, uint32_t) {}); | |
| break; | |
| } | |
| default: | |
| return RPC_UNHANDLED_OPCODE; | |
| } | |
| return RPC_SUCCESS; | |
| } | |
| } // namespace internal | |
| // Handles any opcode generated from the 'libc' client code. | |
| inline RPCStatus handle_libc_opcodes(Server::Port &port, uint32_t num_lanes) { | |
| switch (num_lanes) { | |
| case 1: | |
| return internal::handle_port_impl<1>(port); | |
| case 32: | |
| return internal::handle_port_impl<32>(port); | |
| case 64: | |
| return internal::handle_port_impl<64>(port); | |
| default: | |
| return RPC_ERROR; | |
| } | |
| } | |
| } // namespace rpc | |
| #ifdef _WIN32 | |
| #undef flockfile | |
| #undef funlockfile | |
| #undef fwrite_unlocked | |
| #endif | |
| #endif // LLVM_LIBC_SHARED_RPC_SERVER_H |