[libc-commits] [libc] [llvm] [libc] Support array tags in the RPC dispatch helpers (PR #181395)
Joseph Huber via libc-commits
libc-commits at lists.llvm.org
Mon Feb 16 08:40:24 PST 2026
https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/181395
>From fb722cd7d0169b6c2865cde9ec8f8bdec0e92ae4 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Fri, 13 Feb 2026 12:10:03 -0600
Subject: [PATCH 1/4] [libc] Simplify handling of server-side state for RPC
dispatch
Summary:
This PR just simplifies the logic for which pointer arguments get
marshalled back and forth. Most important we now pass the proper size
to the finalization step so we can now support arrays. A follow-up PR
will implement a way to tag pointers as arrays to make this generic.
---
libc/shared/rpc_dispatch.h | 129 ++++++++++++++++++++-----------------
libc/shared/rpc_util.h | 19 ++++++
2 files changed, 90 insertions(+), 58 deletions(-)
diff --git a/libc/shared/rpc_dispatch.h b/libc/shared/rpc_dispatch.h
index 1a385c1b7d82e..0aa0f6677f6dd 100644
--- a/libc/shared/rpc_dispatch.h
+++ b/libc/shared/rpc_dispatch.h
@@ -51,19 +51,50 @@ template <typename... Ts> struct tuple_bytes {
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 recieve back a server-side pointer to replace
// the client-side pointer in the argument list.
-template <uint64_t Idx, typename Tuple>
-RPC_ATTRS constexpr void prepare_arg(rpc::Client::Port &port, Tuple &t) {
+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 ElemTy = rpc::remove_pointer_t<ArgTy>;
- if constexpr (rpc::is_pointer_v<ArgTy> && rpc::is_complete_v<ElemTy> &&
- !rpc::is_void_v<ElemTy>) {
+ 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 constexpr (rpc::is_same_v<ArgTy, const char *>)
size = rpc::string_length(rpc::get<Idx>(t));
+ else if constexpr (rpc::is_array_ref_v<CallArgTy>)
+ size = rpc::get<Idx>(ct).size * sizeof(rpc::remove_pointer_t<ArgTy>);
else
size = sizeof(rpc::remove_pointer_t<ArgTy>);
port.send_n(rpc::get<Idx>(t), size);
@@ -75,23 +106,15 @@ RPC_ATTRS constexpr void prepare_arg(rpc::Client::Port &port, Tuple &t) {
// Server-side handling of pointer arguments. We recieve 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>
-RPC_ATTRS constexpr void prepare_arg(rpc::Server::Port &port) {
+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>;
- using ElemTy = rpc::remove_pointer_t<ArgTy>;
- if constexpr (rpc::is_pointer_v<ArgTy> && rpc::is_complete_v<ElemTy> &&
- !rpc::is_void_v<ElemTy>) {
- void *args[NUM_LANES]{};
- uint64_t sizes[NUM_LANES]{};
- port.recv_n(args, sizes, [](uint64_t size) {
- if constexpr (rpc::is_same_v<ArgTy, const char *>)
- return malloc(size);
- else
- return malloc(
- sizeof(rpc::remove_const_t<rpc::remove_pointer_t<ArgTy>>));
- });
+ 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 malloc(size); });
port.send([&](rpc::Buffer *buffer, uint32_t id) {
- *reinterpret_cast<ArgTy *>(buffer->data) = static_cast<ArgTy>(args[id]);
+ *reinterpret_cast<ArgTy *>(buffer->data) = static_cast<ArgTy>(ptrs[id]);
});
}
}
@@ -102,14 +125,11 @@ RPC_ATTRS constexpr void prepare_arg(rpc::Server::Port &port) {
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>;
- using ElemTy = rpc::remove_pointer_t<ArgTy>;
- using MemoryTy = rpc::remove_const_t<rpc::remove_pointer_t<ArgTy>> *;
- if constexpr (rpc::is_pointer_v<ArgTy> && !rpc::is_const_v<ArgTy> &&
- rpc::is_complete_v<ElemTy> && !rpc::is_void_v<ElemTy>) {
+ 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<MemoryTy>(rpc::get<Idx>(t));
+ return const_cast<void *>(static_cast<const void *>(rpc::get<Idx>(t)));
});
}
}
@@ -117,30 +137,20 @@ RPC_ATTRS constexpr void finish_arg(rpc::Client::Port &port, Tuple &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, uint64_t Idx, typename Tuple>
-RPC_ATTRS constexpr void finish_arg(rpc::Server::Port &port,
- Tuple (&t)[NUM_LANES]) {
+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>;
- using ElemTy = rpc::remove_pointer_t<ArgTy>;
- if constexpr (rpc::is_pointer_v<ArgTy> && !rpc::is_const_v<ArgTy> &&
- rpc::is_complete_v<ElemTy> && !rpc::is_void_v<ElemTy>) {
- 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<Idx>(t[id]);
- sizes[id] = sizeof(rpc::remove_pointer_t<ArgTy>);
- }
- }
- port.send_n(buffer, sizes);
+ 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_pointer_v<ArgTy> && rpc::is_complete_v<ElemTy> &&
- !rpc::is_void_v<ElemTy>) {
+ 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 *>(rpc::get<Idx>(t[id]))));
+ free(const_cast<void *>(static_cast<const void *>(ptrs[id])));
}
}
}
@@ -148,15 +158,16 @@ RPC_ATTRS constexpr void finish_arg(rpc::Server::Port &port,
// 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, uint64_t... Is>
+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), ...);
+ (prepare_arg<Is>(port, t, ct), ...);
}
-template <uint32_t NUM_LANES, typename Tuple, uint64_t... Is>
-RPC_ATTRS constexpr void prepare_args(rpc::Server::Port &port,
+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), ...);
+ (prepare_arg<NUM_LANES, Tuple, Is>(port, state), ...);
}
// Performs the preparation in reverse, copying back any modified values.
@@ -165,11 +176,10 @@ 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, uint64_t... Is>
-RPC_ATTRS constexpr void finish_args(rpc::Server::Port &port,
- Tuple (&t)[NUM_LANES],
+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, Is>(port, t), ...);
+ (finish_arg<NUM_LANES, Tuple, Is>(port, state), ...);
}
} // namespace
@@ -181,7 +191,7 @@ dispatch(rpc::Client &client, FnTy, CallArgs... args) {
using Traits = function_traits<FnTy>;
using RetTy = typename Traits::return_type;
using TupleTy = typename Traits::arg_types;
- using Bytes = tuple_bytes<CallArgs...>;
+ using Bytes = tuple_bytes<rpc::remove_array_ref_t<CallArgs>...>;
static_assert(sizeof...(CallArgs) == Traits::ARITY,
"Argument count mismatch");
@@ -193,8 +203,10 @@ dispatch(rpc::Client &client, FnTy, CallArgs... args) {
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, rpc::make_index_sequence<Traits::ARITY>{});
+ 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);
@@ -224,8 +236,9 @@ RPC_ATTRS constexpr void invoke(rpc::Server::Port &port, FnTy fn) {
using Bytes = tuple_bytes<TupleTy>;
// Recieve pointer data from the host and pack it in server-side memory.
+ MarshalledState<NUM_LANES, TupleTy> state{};
rpc::prepare_args<NUM_LANES, TupleTy>(
- port, rpc::make_index_sequence<Traits::ARITY>{});
+ port, state, rpc::make_index_sequence<Traits::ARITY>{});
// Get the argument list from the client.
typename Bytes::array_type arg_buf[NUM_LANES]{};
@@ -252,8 +265,8 @@ RPC_ATTRS constexpr void invoke(rpc::Server::Port &port, FnTy fn) {
}
// Send any potentially modified pointer arguments back to the client.
- rpc::finish_args<NUM_LANES>(port, args,
- rpc::make_index_sequence<Traits::ARITY>{});
+ 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.
diff --git a/libc/shared/rpc_util.h b/libc/shared/rpc_util.h
index 171caff1140f9..d73c32ad75f08 100644
--- a/libc/shared/rpc_util.h
+++ b/libc/shared/rpc_util.h
@@ -106,6 +106,25 @@ template <typename T, typename... Args>
RPC_ATTRS constexpr bool is_trivially_constructible_v =
is_trivially_constructible<T>::value;
+/// Tag type to indicate an array of elements being passed through RPC.
+template <typename T> struct array_ref {
+ T *data;
+ uint64_t size;
+ RPC_ATTRS operator T *() const { return data; }
+};
+
+template <typename T> struct is_array_ref : type_constant<bool, false> {};
+template <typename T>
+struct is_array_ref<array_ref<T>> : type_constant<bool, true> {};
+template <typename T>
+RPC_ATTRS constexpr bool is_array_ref_v = is_array_ref<T>::value;
+
+template <typename T> struct remove_array_ref : type_identity<T> {};
+template <typename T>
+struct remove_array_ref<array_ref<T>> : type_identity<T *> {};
+template <typename T>
+using remove_array_ref_t = typename remove_array_ref<T>::type;
+
template <bool B, typename T, typename F>
struct conditional : type_identity<T> {};
template <typename T, typename F>
>From 3242f3aa15d969e6465c1cc99ab2fb6c87d7df6c Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Sat, 14 Feb 2026 18:56:22 -0600
Subject: [PATCH 2/4] test
---
offload/test/libc/rpc_callback.cpp | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/offload/test/libc/rpc_callback.cpp b/offload/test/libc/rpc_callback.cpp
index f313541e58d1d..3502faccb64b4 100644
--- a/offload/test/libc/rpc_callback.cpp
+++ b/offload/test/libc/rpc_callback.cpp
@@ -32,6 +32,7 @@ constexpr uint32_t CONST_PTR_OPCODE = 4;
constexpr uint32_t STRING_OPCODE = 5;
constexpr uint32_t EMPTY_OPCODE = 6;
constexpr uint32_t DIVERGENT_OPCODE = 7;
+constexpr uint32_t ARRAY_SUM_OPCODE = 8;
//===------------------------------------------------------------------------===
// Server-side implementations.
@@ -82,6 +83,14 @@ void divergent(int *p) {
*p = *p;
}
+// 8. Array argument via array_ref.
+int sum_array(const int *arr, int n) {
+ int s = 0;
+ for (int i = 0; i < n; ++i)
+ s += arr[i];
+ return s;
+}
+
//===------------------------------------------------------------------------===
// RPC client dispatch.
//===------------------------------------------------------------------------===
@@ -110,6 +119,11 @@ int empty() { return rpc::dispatch<EMPTY_OPCODE>(client, empty); }
void divergent(int *p) {
rpc::dispatch<DIVERGENT_OPCODE>(client, divergent, p);
}
+
+int sum_array(const int *arr, int n) {
+ return rpc::dispatch<ARRAY_SUM_OPCODE>(
+ client, sum_array, rpc::array_ref<const int>{arr, uint64_t(n)}, n);
+}
#pragma omp end declare variant
//===------------------------------------------------------------------------===
@@ -143,6 +157,9 @@ rpc::Status handleOpcodesImpl(rpc::Server::Port &Port) {
*p = *p;
});
break;
+ case ARRAY_SUM_OPCODE:
+ rpc::invoke<NUM_LANES>(Port, sum_array);
+ break;
default:
return rpc::RPC_UNHANDLED_OPCODE;
}
@@ -203,6 +220,11 @@ int main() {
if (id % 2)
divergent(&id);
assert(id == omp_get_thread_num());
+
+ // 8. Array sum via array_ref.
+ int arr[4] = {1, 2, 3, 4};
+ int total = sum_array(arr, 4);
+ assert(total == 10);
}
printf("PASS\n");
>From 35859eceddcfdef1b3c0bc9f62bbade0ee6f2d7e Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Sun, 15 Feb 2026 10:36:51 -0600
Subject: [PATCH 3/4] Fix noexcept
---
libc/shared/rpc_util.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/libc/shared/rpc_util.h b/libc/shared/rpc_util.h
index d73c32ad75f08..f641434aca2c0 100644
--- a/libc/shared/rpc_util.h
+++ b/libc/shared/rpc_util.h
@@ -466,6 +466,12 @@ template <typename R, typename... Args> struct function_traits<R (*)(Args...)> {
using arg_types = rpc::tuple<Args...>;
static constexpr uint64_t ARITY = sizeof...(Args);
};
+template <typename R, typename... Args>
+struct function_traits<R (*)(Args...) noexcept> {
+ using return_type = R;
+ using arg_types = rpc::tuple<Args...>;
+ static constexpr uint64_t ARITY = sizeof...(Args);
+};
template <typename T> T &&declval();
template <typename T>
struct function_traits
>From b8f97eb6d3806b9cab894b9e03cdea15c8b33deb Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Mon, 16 Feb 2026 10:40:03 -0600
Subject: [PATCH 4/4] Fix strict aliasing warnings
---
libc/shared/rpc_dispatch.h | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/libc/shared/rpc_dispatch.h b/libc/shared/rpc_dispatch.h
index 0aa0f6677f6dd..0bcb92515b223 100644
--- a/libc/shared/rpc_dispatch.h
+++ b/libc/shared/rpc_dispatch.h
@@ -99,7 +99,9 @@ RPC_ATTRS constexpr void prepare_arg(rpc::Client::Port &port, Tuple &t,
size = sizeof(rpc::remove_pointer_t<ArgTy>);
port.send_n(rpc::get<Idx>(t), size);
port.recv([&](rpc::Buffer *buffer, uint32_t) {
- rpc::get<Idx>(t) = *reinterpret_cast<ArgTy *>(buffer->data);
+ ArgTy val;
+ rpc::rpc_memcpy(&val, buffer->data, sizeof(ArgTy));
+ rpc::get<Idx>(t) = val;
});
}
}
@@ -114,7 +116,8 @@ RPC_ATTRS constexpr void prepare_arg(rpc::Server::Port &port, State &&state) {
auto &sizes = state.sizes[rpc::marshalled_index_v<Tuple, Idx>];
port.recv_n(ptrs, sizes, [](uint64_t size) { return malloc(size); });
port.send([&](rpc::Buffer *buffer, uint32_t id) {
- *reinterpret_cast<ArgTy *>(buffer->data) = static_cast<ArgTy>(ptrs[id]);
+ ArgTy val = static_cast<ArgTy>(ptrs[id]);
+ rpc::rpc_memcpy(buffer->data, &val, sizeof(ArgTy));
});
}
}
More information about the libc-commits
mailing list