[libc-commits] [libc] [libc] Simplify handling of server-side state for RPC dispatch (PR #181395)

Joseph Huber via libc-commits libc-commits at lists.llvm.org
Fri Feb 13 10:15:34 PST 2026


https://github.com/jhuber6 created https://github.com/llvm/llvm-project/pull/181395

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.


>From 9739efcb23e353057a18682cb6b7cd13ec9cfde8 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] [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 | 110 +++++++++++++++++++------------------
 1 file changed, 58 insertions(+), 52 deletions(-)

diff --git a/libc/shared/rpc_dispatch.h b/libc/shared/rpc_dispatch.h
index 1a385c1b7d82e..7789e6cdc7dd7 100644
--- a/libc/shared/rpc_dispatch.h
+++ b/libc/shared/rpc_dispatch.h
@@ -51,15 +51,42 @@ 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_LANES][NUM_PTRS]{};
+  void *ptrs[NUM_LANES][NUM_PTRS]{};
+};
+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) {
   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>) {
+  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 *>)
@@ -75,23 +102,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 +121,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 +133,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])));
     }
   }
 }
@@ -153,10 +159,10 @@ RPC_ATTRS constexpr void prepare_args(rpc::Client::Port &port, Tuple &t,
                                       rpc::index_sequence<Is...>) {
   (prepare_arg<Is>(port, t), ...);
 }
-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 +171,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
 
@@ -224,8 +229,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 +258,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.



More information about the libc-commits mailing list