[libc-commits] [libc] [llvm] [libc] Make rpc_server.h independent from libc internals (PR #190423)
via libc-commits
libc-commits at lists.llvm.org
Fri Apr 3 15:52:19 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-offload
Author: Joseph Huber (jhuber6)
<details>
<summary>Changes</summary>
Summary:
It was very convenient to have the RPC server use the internal libc
printing utilities, but it caused a lot of problems. The LLVM libc
internals were never meant to be included arbitrarily and we completely
bypassed this restrictino. Furthermore it prevented us from installing
and using these libraries in other contexts. There was a whole host of
hacks around this, leading to endless PPC problems, compiler errors,
etc.
This PR re-uses the old minified format parser I used to use for the GPU
case. We simply parse the formats to get the size, then copy the
strings. The actual printing instead is done by locking the output file
and repeatedly building up the string using the host's `printf`. Slight
test modifications because we no longer can depend on the specific user
printf specification, it may not handle null or binary values for
example.
---
Patch is 49.47 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/190423.diff
6 Files Affected:
- (modified) libc/shared/CMakeLists.txt (+1)
- (modified) libc/shared/rpc_server.h (+764-8)
- (removed) libc/src/__support/RPC/rpc_server.h (-606)
- (modified) libc/src/__support/arg_list.h (-40)
- (modified) libc/test/integration/src/stdio/gpu/printf_test.cpp (+4-18)
- (modified) offload/plugins-nextgen/common/src/RPC.cpp (+1-1)
``````````diff
diff --git a/libc/shared/CMakeLists.txt b/libc/shared/CMakeLists.txt
index 3cbaa7c14fcbe..7ceddd3e464dc 100644
--- a/libc/shared/CMakeLists.txt
+++ b/libc/shared/CMakeLists.txt
@@ -2,6 +2,7 @@ set(LLVM_LIBC_SHARED_RPC_EXPORT_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/rpc.h"
"${CMAKE_CURRENT_SOURCE_DIR}/rpc_util.h"
"${CMAKE_CURRENT_SOURCE_DIR}/rpc_dispatch.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/rpc_server.h"
)
# Install the freestanding RPC interface to the compiler tree.
diff --git a/libc/shared/rpc_server.h b/libc/shared/rpc_server.h
index 46e35f13f0eac..5511938b6487a 100644
--- a/libc/shared/rpc_server.h
+++ b/libc/shared/rpc_server.h
@@ -1,4 +1,4 @@
-//===-- Shared RPC server interface -----------------------------*- C++ -*-===//
+//===-- 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.
@@ -9,15 +9,771 @@
#ifndef LLVM_LIBC_SHARED_RPC_SERVER_H
#define LLVM_LIBC_SHARED_RPC_SERVER_H
-#include "libc_common.h"
-#include "src/__support/RPC/rpc_server.h"
+#include "rpc.h"
+#include "rpc_opcodes.h"
-namespace LIBC_NAMESPACE_DECL {
-namespace shared {
+#include <stdio.h>
+#include <stdlib.h>
-using LIBC_NAMESPACE::rpc::handle_libc_opcodes;
+#ifdef _WIN32
+#define flockfile _lock_file
+#define funlockfile _unlock_file
+#define fwrite_unlocked _fwrite_nolock
+#endif
-} // namespace shared
-} // namespace LIBC_NAMESPACE_DECL
+namespace rpc {
+
+// Minimal replacement for 'std::vector' that works for trivial types.
+template <typename T> class TempVector {
+ T *data_;
+ size_t current;
+ size_t capacity;
+
+public:
+ inline TempVector() : data_(nullptr), current(0), capacity(0) {}
+
+ inline ~TempVector() { free(data_); }
+
+ inline void push_back(const T &value) {
+ if (current == capacity)
+ grow();
+ data_[current++] = value;
+ }
+
+ inline void pop_back() { --current; }
+
+ inline bool empty() const { return current == 0; }
+
+ inline size_t size() const { return current; }
+
+ inline T &operator[](size_t index) { return data_[index]; }
+
+ inline T &back() { return data_[current - 1]; }
+
+private:
+ inline 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 {
+ inline char *alloc(size_t size) {
+ storage.push_back(reinterpret_cast<char *>(malloc(size)));
+ return storage.back();
+ }
+
+ inline ~TempStorage() {
+ for (size_t i = 0; i < storage.size(); ++i)
+ free(storage[i]);
+ }
+
+ 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);
+ }
+
+ inline 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;
+
+ inline 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;
+ __builtin_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>();
+ __builtin_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;
+};
+
+// Dispatch helper that passes dynamic '*' width/precision values to fprintf.
+template <typename T>
+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;
+
+ 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)
+ ret += fwrite_unlocked(fmt + prev, 1, start - prev, file);
+
+ // 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];
+ __builtin_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();
+ }
+ ret += fprintf_with_stars(file, buf, num_stars, star_vals, str);
+ break;
+ }
+ case 'n':
+ break;
+ case 'p':
+ ret += 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;
+ __builtin_memcpy(&d, &spec.raw_value, sizeof(double));
+ if (spec.is_long)
+ ret += fprintf_with_stars(file, buf, num_stars, star_vals,
+ static_cast<long double>(d));
+ else
+ ret += fprintf_with_stars(file, buf, num_stars, star_vals, d);
+ break;
+ }
+ default:
+ if (spec.is_long)
+ ret +=
+ fprintf_with_stars(file, buf, num_stars, star_vals, spec.raw_value);
+ else
+ ret += 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)
+ ret += fwrite_unlocked(fmt + prev, 1, parser.pos() - prev, file);
+
+ return ret;
+}
+
+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) {
+ __builtin_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] = ::fclose(file);
+ });
+ break;
+ }
+ case LIBC_EXIT: {
+ port.recv_and_send([](Buffer *, uint32_t) {});
+ port.recv([](Buffer *buffer, uint32_t) {
+ int status = 0;
+ __builtin_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] = feof(to_stream(buffer->data[0]));
+ });
+ break;
+ }
+ cas...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/190423
More information about the libc-commits
mailing list