[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 13:15:05 PST 2026


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

>From 01d492a88e6052237a6d5b13529756e27a72311e 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 ff4f357378f2a..ceed703b07357 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);
@@ -223,8 +235,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]{};
@@ -251,8 +264,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 1d7190a6d66c680450d8016ac55f6788815cbb13 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 73026c4f7e7b011ce2aab3776a81af67210434af 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 947016bc205a6a1dfa413c7fb440d5fc3b40c1db 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 ceed703b07357..ef9ca8d8a101d 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