[llvm] [ORC] Add synchronous helper APIs to CallViaEPC.h (PR #170743)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Thu Dec 4 12:52:57 PST 2025


https://github.com/lhames created https://github.com/llvm/llvm-project/pull/170743

This commit adds synchronous variants of callViaEPC and associated APIs ( EPCCaller, EPCCall), that take a promise as their first argument and block on the resulting future, returning a result. The promise object is passed to the asynchronous variant of callViaEPC to make the call.

The type of the promise argument is a template parameter, so types other than std::promise may be used. The promise type must provide a `get_future` operation to get a future for the result, and the future type must provide a `get` operation to wait on the result (compatible with std::promise / std::future).

The value type of the promise must be Expected<T> or Error, and determines the return type of the synchronous call. E.g.

```
Expected<int32_t> Result =
    callViaEPC(std::promise<Expected<int32_t>>(), EPC, Serializer, Args...);
```

>From 55979460365762017d7cc218f4193f3c98a00a2a Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at gmail.com>
Date: Thu, 4 Dec 2025 15:23:01 +1100
Subject: [PATCH] [ORC] Add synchronous helper APIs to CallViaEPC.h

This commit adds synchronous variants of callViaEPC and associated APIs (
EPCCaller, EPCCall), that take a promise as their first argument and block on
the resulting future, returning a result. The promise object is passed to the
asynchronous variant of callViaEPC to make the call.

The type of the promise argument is a template parameter, so types other than
std::promise may be used. The promise type must provide a `get_future`
operation to get a future for the result, and the future type must provide a
`get` operation to wait on the result (compatible with std::promise /
std::future).

The value type of the promise must be Expected<T> or Error, and determines the
return type of the synchronous call. E.g.

```
Expected<int32_t> Result =
    callViaEPC(std::promise<Expected<int32_t>>(), EPC, Serializer, Args...);
```
---
 .../llvm/ExecutionEngine/Orc/CallViaEPC.h     | 59 +++++++++++++-----
 .../ExecutionEngine/Orc/CallSPSViaEPCTest.cpp | 61 ++++++++++++++++++-
 2 files changed, 104 insertions(+), 16 deletions(-)

diff --git a/llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h b/llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h
index 36acf05cb48e9..7797250fa3016 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h
@@ -17,6 +17,8 @@
 #include "llvm/ExecutionEngine/Orc/CallableTraitsHelper.h"
 #include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h"
 
+#include <type_traits>
+
 namespace llvm::orc {
 
 namespace detail {
@@ -34,16 +36,17 @@ template <typename HandlerT> struct HandlerTraits {
 
 } // namespace detail
 
-/// Call a wrapper function via EPC.
-template <typename HandlerT, typename Serializer, typename... ArgTs>
-void callViaEPC(HandlerT &&H, ExecutorProcessControl &EPC, Serializer S,
-                ExecutorSymbolDef Fn, ArgTs &&...Args) {
-  using RetT = typename detail::HandlerTraits<HandlerT>::RetT;
+/// Call a wrapper function via EPC asynchronously.
+template <typename HandlerFn, typename Serializer, typename... ArgTs>
+std::enable_if_t<std::is_invocable_v<HandlerFn, Error>>
+callViaEPC(HandlerFn &&H, ExecutorProcessControl &EPC, Serializer S,
+           ExecutorSymbolDef Fn, ArgTs &&...Args) {
+  using RetT = typename detail::HandlerTraits<HandlerFn>::RetT;
 
   if (auto ArgBytes = S.serialize(std::forward<ArgTs>(Args)...))
     EPC.callWrapperAsync(
         Fn.getAddress(),
-        [S = std::move(S), H = std::forward<HandlerT>(H)](
+        [S = std::move(S), H = std::forward<HandlerFn>(H)](
             shared::WrapperFunctionResult R) mutable {
           if (const char *ErrMsg = R.getOutOfBandError())
             H(make_error<StringError>(ErrMsg, inconvertibleErrorCode()));
@@ -55,6 +58,25 @@ void callViaEPC(HandlerT &&H, ExecutorProcessControl &EPC, Serializer S,
     H(ArgBytes.takeError());
 }
 
+/// Call a wrapper function via EPC synchronously using the given promise.
+///
+/// This performs a blocking call by making an asynchronous call to set the
+/// promise and waiting on a future.
+///
+/// Blocking calls should only be used for convenience by ORC clients, never
+/// internally.
+template <typename PromiseT, typename Serializer, typename... ArgTs>
+std::enable_if_t<!std::is_invocable_v<PromiseT, Error>,
+                 decltype(std::declval<PromiseT>().get_future().get())>
+callViaEPC(PromiseT &&P, ExecutorProcessControl &EPC, Serializer S,
+           ExecutorSymbolDef Fn, ArgTs &&...Args) {
+  auto F = P.get_future();
+  using RetT = decltype(F.get());
+  callViaEPC([P = std::move(P)](RetT R) mutable { P.set_value(std::move(R)); },
+             EPC, std::move(S), std::move(Fn), std::forward<ArgTs>(Args)...);
+  return F.get();
+}
+
 /// Encapsulates calls via EPC to any function that's compatible with the given
 /// serialization scheme.
 template <typename Serializer> class EPCCaller {
@@ -65,11 +87,15 @@ template <typename Serializer> class EPCCaller {
   // TODO: Add an ExecutionSession constructor once ExecutionSession has been
   //       moved to its own header.
 
-  // Async call version.
-  template <typename HandlerT, typename... ArgTs>
-  void operator()(HandlerT &&H, ExecutorSymbolDef Fn, ArgTs &&...Args) {
-    callViaEPC(std::forward<HandlerT>(H), EPC, S, Fn,
-               std::forward<ArgTs>(Args)...);
+  // Make a call to the given function using callViaEPC.
+  //
+  // The PromiseOrHandlerT value is forwarded. Its type will determine both the
+  // return value type and the dispatch method (asynchronous vs synchronous).
+  template <typename PromiseOrHandlerT, typename... ArgTs>
+  decltype(auto) operator()(PromiseOrHandlerT &&R, ExecutorSymbolDef Fn,
+                            ArgTs &&...Args) {
+    return callViaEPC(std::forward<PromiseOrHandlerT>(R), EPC, S, Fn,
+                      std::forward<ArgTs>(Args)...);
   }
 
 private:
@@ -87,9 +113,14 @@ template <typename Serializer> class EPCCall {
   // TODO: Add an ExecutionSession constructor once ExecutionSession has been
   //       moved to its own header.
 
-  template <typename HandlerT, typename... ArgTs>
-  void operator()(HandlerT &&H, ArgTs &&...Args) {
-    Caller(std::forward<HandlerT>(H), Fn, std::forward<ArgTs>(Args)...);
+  // Make a call using callViaEPC.
+  //
+  // The PromiseOrHandlerT value is forwarded. Its type will determine both the
+  // return value type and the dispatch method (asynchronous vs synchronous).
+  template <typename PromiseOrHandlerT, typename... ArgTs>
+  decltype(auto) operator()(PromiseOrHandlerT &&R, ArgTs &&...Args) {
+    return Caller(std::forward<PromiseOrHandlerT>(R), Fn,
+                  std::forward<ArgTs>(Args)...);
   }
 
 private:
diff --git a/llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp b/llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp
index 6ad5835ed6e8f..148947153d973 100644
--- a/llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp
+++ b/llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp
@@ -11,6 +11,8 @@
 
 #include "llvm/Testing/Support/Error.h"
 
+#include <future>
+
 #include "gtest/gtest.h"
 
 using namespace llvm;
@@ -26,7 +28,7 @@ static CWrapperFunctionResult mainWrapper(const char *ArgData, size_t ArgSize) {
       .release();
 }
 
-TEST(CallSPSViaEPCTest, CallMainViaCaller) {
+TEST(CallSPSViaEPCTest, CallMainViaCallerAsync) {
   auto EPC = cantFail(SelfExecutorProcessControl::Create());
   SPSEPCCaller<int32_t(SPSSequence<SPSString>)> C(*EPC);
   std::vector<std::string> Args;
@@ -59,7 +61,7 @@ TEST(CallSPSViaEPCTest, CallMainViaCaller) {
   EXPECT_EQ(**R4, 0);
 }
 
-TEST(CallSPSViaEPCTest, CallMainViaGenericCall) {
+TEST(CallSPSViaEPCTest, CallMainViaGenericCallAsync) {
   auto EPC = cantFail(SelfExecutorProcessControl::Create());
   SPSEPCCall<int32_t(SPSSequence<SPSString>)> C(
       *EPC, ExecutorSymbolDef::fromPtr(mainWrapper));
@@ -88,3 +90,58 @@ TEST(CallSPSViaEPCTest, CallMainViaGenericCall) {
   ASSERT_THAT_EXPECTED(*R4, Succeeded());
   EXPECT_EQ(**R4, 0);
 }
+
+TEST(CallSPSViaEPCTest, CallMainViaCallerSync) {
+  auto EPC = cantFail(SelfExecutorProcessControl::Create());
+  SPSEPCCaller<int32_t(SPSSequence<SPSString>)> C(*EPC);
+  std::vector<std::string> Args;
+
+  Expected<int32_t> R1 = C(std::promise<Expected<int32_t>>(),
+                           ExecutorSymbolDef::fromPtr(mainWrapper), Args);
+  ASSERT_THAT_EXPECTED(R1, Succeeded());
+  EXPECT_EQ(*R1, 0);
+
+  Args.push_back("foo");
+  Expected<int32_t> R2 = C(std::promise<Expected<int32_t>>(),
+                           ExecutorSymbolDef::fromPtr(mainWrapper), Args);
+  ASSERT_THAT_EXPECTED(R2, Succeeded());
+  EXPECT_EQ(*R2, 1);
+
+  Args.push_back("foo");
+  Expected<int32_t> R3 = C(std::promise<Expected<int32_t>>(),
+                           ExecutorSymbolDef::fromPtr(mainWrapper), Args);
+  ASSERT_THAT_EXPECTED(R3, Succeeded());
+  EXPECT_EQ(*R3, 2);
+
+  Args.clear();
+  Expected<int32_t> R4 = C(std::promise<Expected<int32_t>>(),
+                           ExecutorSymbolDef::fromPtr(mainWrapper), Args);
+  ASSERT_THAT_EXPECTED(R4, Succeeded());
+  EXPECT_EQ(*R4, 0);
+}
+
+TEST(CallSPSViaEPCTest, CallMainViaGenericCallSync) {
+  auto EPC = cantFail(SelfExecutorProcessControl::Create());
+  SPSEPCCall<int32_t(SPSSequence<SPSString>)> C(
+      *EPC, ExecutorSymbolDef::fromPtr(mainWrapper));
+  std::vector<std::string> Args;
+
+  Expected<int32_t> R1 = C(std::promise<Expected<int32_t>>(), Args);
+  ASSERT_THAT_EXPECTED(R1, Succeeded());
+  EXPECT_EQ(*R1, 0);
+
+  Args.push_back("foo");
+  Expected<int32_t> R2 = C(std::promise<Expected<int32_t>>(), Args);
+  ASSERT_THAT_EXPECTED(R2, Succeeded());
+  EXPECT_EQ(*R2, 1);
+
+  Args.push_back("foo");
+  Expected<int32_t> R3 = C(std::promise<Expected<int32_t>>(), Args);
+  ASSERT_THAT_EXPECTED(R3, Succeeded());
+  EXPECT_EQ(*R3, 2);
+
+  Args.clear();
+  Expected<int32_t> R4 = C(std::promise<Expected<int32_t>>(), Args);
+  ASSERT_THAT_EXPECTED(R4, Succeeded());
+  EXPECT_EQ(*R4, 0);
+}



More information about the llvm-commits mailing list