Skip to content

Commit 5ca53a2

Browse files
authored
[ORC] Add synchronous helper APIs to CallViaEPC.h (#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...); ```
1 parent e209b8b commit 5ca53a2

File tree

2 files changed

+106
-16
lines changed

2 files changed

+106
-16
lines changed

llvm/include/llvm/ExecutionEngine/Orc/CallViaEPC.h

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
#include "llvm/ExecutionEngine/Orc/CallableTraitsHelper.h"
1818
#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h"
19+
#include "llvm/Support/Error.h"
20+
#include "llvm/Support/MSVCErrorWorkarounds.h"
21+
22+
#include <type_traits>
1923

2024
namespace llvm::orc {
2125

@@ -34,16 +38,17 @@ template <typename HandlerT> struct HandlerTraits {
3438

3539
} // namespace detail
3640

37-
/// Call a wrapper function via EPC.
38-
template <typename HandlerT, typename Serializer, typename... ArgTs>
39-
void callViaEPC(HandlerT &&H, ExecutorProcessControl &EPC, Serializer S,
40-
ExecutorSymbolDef Fn, ArgTs &&...Args) {
41-
using RetT = typename detail::HandlerTraits<HandlerT>::RetT;
41+
/// Call a wrapper function via EPC asynchronously.
42+
template <typename HandlerFn, typename Serializer, typename... ArgTs>
43+
std::enable_if_t<std::is_invocable_v<HandlerFn, Error>>
44+
callViaEPC(HandlerFn &&H, ExecutorProcessControl &EPC, Serializer S,
45+
ExecutorSymbolDef Fn, ArgTs &&...Args) {
46+
using RetT = typename detail::HandlerTraits<HandlerFn>::RetT;
4247

4348
if (auto ArgBytes = S.serialize(std::forward<ArgTs>(Args)...))
4449
EPC.callWrapperAsync(
4550
Fn.getAddress(),
46-
[S = std::move(S), H = std::forward<HandlerT>(H)](
51+
[S = std::move(S), H = std::forward<HandlerFn>(H)](
4752
shared::WrapperFunctionResult R) mutable {
4853
if (const char *ErrMsg = R.getOutOfBandError())
4954
H(make_error<StringError>(ErrMsg, inconvertibleErrorCode()));
@@ -55,6 +60,25 @@ void callViaEPC(HandlerT &&H, ExecutorProcessControl &EPC, Serializer S,
5560
H(ArgBytes.takeError());
5661
}
5762

63+
/// Call a wrapper function via EPC synchronously using the given promise.
64+
///
65+
/// This performs a blocking call by making an asynchronous call to set the
66+
/// promise and waiting on a future.
67+
///
68+
/// Blocking calls should only be used for convenience by ORC clients, never
69+
/// internally.
70+
template <typename PromiseT, typename Serializer, typename... ArgTs>
71+
std::enable_if_t<!std::is_invocable_v<PromiseT, Error>,
72+
decltype(std::declval<PromiseT>().get_future().get())>
73+
callViaEPC(PromiseT &&P, ExecutorProcessControl &EPC, Serializer S,
74+
ExecutorSymbolDef Fn, ArgTs &&...Args) {
75+
auto F = P.get_future();
76+
using RetT = decltype(F.get());
77+
callViaEPC([P = std::move(P)](RetT R) mutable { P.set_value(std::move(R)); },
78+
EPC, std::move(S), std::move(Fn), std::forward<ArgTs>(Args)...);
79+
return F.get();
80+
}
81+
5882
/// Encapsulates calls via EPC to any function that's compatible with the given
5983
/// serialization scheme.
6084
template <typename Serializer> class EPCCaller {
@@ -65,11 +89,15 @@ template <typename Serializer> class EPCCaller {
6589
// TODO: Add an ExecutionSession constructor once ExecutionSession has been
6690
// moved to its own header.
6791

68-
// Async call version.
69-
template <typename HandlerT, typename... ArgTs>
70-
void operator()(HandlerT &&H, ExecutorSymbolDef Fn, ArgTs &&...Args) {
71-
callViaEPC(std::forward<HandlerT>(H), EPC, S, Fn,
72-
std::forward<ArgTs>(Args)...);
92+
// Make a call to the given function using callViaEPC.
93+
//
94+
// The PromiseOrHandlerT value is forwarded. Its type will determine both the
95+
// return value type and the dispatch method (asynchronous vs synchronous).
96+
template <typename PromiseOrHandlerT, typename... ArgTs>
97+
decltype(auto) operator()(PromiseOrHandlerT &&R, ExecutorSymbolDef Fn,
98+
ArgTs &&...Args) {
99+
return callViaEPC(std::forward<PromiseOrHandlerT>(R), EPC, S, Fn,
100+
std::forward<ArgTs>(Args)...);
73101
}
74102

75103
private:
@@ -87,9 +115,14 @@ template <typename Serializer> class EPCCall {
87115
// TODO: Add an ExecutionSession constructor once ExecutionSession has been
88116
// moved to its own header.
89117

90-
template <typename HandlerT, typename... ArgTs>
91-
void operator()(HandlerT &&H, ArgTs &&...Args) {
92-
Caller(std::forward<HandlerT>(H), Fn, std::forward<ArgTs>(Args)...);
118+
// Make a call using callViaEPC.
119+
//
120+
// The PromiseOrHandlerT value is forwarded. Its type will determine both the
121+
// return value type and the dispatch method (asynchronous vs synchronous).
122+
template <typename PromiseOrHandlerT, typename... ArgTs>
123+
decltype(auto) operator()(PromiseOrHandlerT &&R, ArgTs &&...Args) {
124+
return Caller(std::forward<PromiseOrHandlerT>(R), Fn,
125+
std::forward<ArgTs>(Args)...);
93126
}
94127

95128
private:

llvm/unittests/ExecutionEngine/Orc/CallSPSViaEPCTest.cpp

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#include "llvm/Testing/Support/Error.h"
1313

14+
#include <future>
15+
1416
#include "gtest/gtest.h"
1517

1618
using namespace llvm;
@@ -26,7 +28,7 @@ static CWrapperFunctionResult mainWrapper(const char *ArgData, size_t ArgSize) {
2628
.release();
2729
}
2830

29-
TEST(CallSPSViaEPCTest, CallMainViaCaller) {
31+
TEST(CallSPSViaEPCTest, CallMainViaCallerAsync) {
3032
auto EPC = cantFail(SelfExecutorProcessControl::Create());
3133
SPSEPCCaller<int32_t(SPSSequence<SPSString>)> C(*EPC);
3234
std::vector<std::string> Args;
@@ -59,7 +61,7 @@ TEST(CallSPSViaEPCTest, CallMainViaCaller) {
5961
EXPECT_EQ(**R4, 0);
6062
}
6163

62-
TEST(CallSPSViaEPCTest, CallMainViaGenericCall) {
64+
TEST(CallSPSViaEPCTest, CallMainViaGenericCallAsync) {
6365
auto EPC = cantFail(SelfExecutorProcessControl::Create());
6466
SPSEPCCall<int32_t(SPSSequence<SPSString>)> C(
6567
*EPC, ExecutorSymbolDef::fromPtr(mainWrapper));
@@ -88,3 +90,58 @@ TEST(CallSPSViaEPCTest, CallMainViaGenericCall) {
8890
ASSERT_THAT_EXPECTED(*R4, Succeeded());
8991
EXPECT_EQ(**R4, 0);
9092
}
93+
94+
TEST(CallSPSViaEPCTest, CallMainViaCallerSync) {
95+
auto EPC = cantFail(SelfExecutorProcessControl::Create());
96+
SPSEPCCaller<int32_t(SPSSequence<SPSString>)> C(*EPC);
97+
std::vector<std::string> Args;
98+
99+
Expected<int32_t> R1 = C(std::promise<MSVCPExpected<int32_t>>(),
100+
ExecutorSymbolDef::fromPtr(mainWrapper), Args);
101+
ASSERT_THAT_EXPECTED(R1, Succeeded());
102+
EXPECT_EQ(*R1, 0);
103+
104+
Args.push_back("foo");
105+
Expected<int32_t> R2 = C(std::promise<MSVCPExpected<int32_t>>(),
106+
ExecutorSymbolDef::fromPtr(mainWrapper), Args);
107+
ASSERT_THAT_EXPECTED(R2, Succeeded());
108+
EXPECT_EQ(*R2, 1);
109+
110+
Args.push_back("foo");
111+
Expected<int32_t> R3 = C(std::promise<MSVCPExpected<int32_t>>(),
112+
ExecutorSymbolDef::fromPtr(mainWrapper), Args);
113+
ASSERT_THAT_EXPECTED(R3, Succeeded());
114+
EXPECT_EQ(*R3, 2);
115+
116+
Args.clear();
117+
Expected<int32_t> R4 = C(std::promise<MSVCPExpected<int32_t>>(),
118+
ExecutorSymbolDef::fromPtr(mainWrapper), Args);
119+
ASSERT_THAT_EXPECTED(R4, Succeeded());
120+
EXPECT_EQ(*R4, 0);
121+
}
122+
123+
TEST(CallSPSViaEPCTest, CallMainViaGenericCallSync) {
124+
auto EPC = cantFail(SelfExecutorProcessControl::Create());
125+
SPSEPCCall<int32_t(SPSSequence<SPSString>)> C(
126+
*EPC, ExecutorSymbolDef::fromPtr(mainWrapper));
127+
std::vector<std::string> Args;
128+
129+
Expected<int32_t> R1 = C(std::promise<MSVCPExpected<int32_t>>(), Args);
130+
ASSERT_THAT_EXPECTED(R1, Succeeded());
131+
EXPECT_EQ(*R1, 0);
132+
133+
Args.push_back("foo");
134+
Expected<int32_t> R2 = C(std::promise<MSVCPExpected<int32_t>>(), Args);
135+
ASSERT_THAT_EXPECTED(R2, Succeeded());
136+
EXPECT_EQ(*R2, 1);
137+
138+
Args.push_back("foo");
139+
Expected<int32_t> R3 = C(std::promise<MSVCPExpected<int32_t>>(), Args);
140+
ASSERT_THAT_EXPECTED(R3, Succeeded());
141+
EXPECT_EQ(*R3, 2);
142+
143+
Args.clear();
144+
Expected<int32_t> R4 = C(std::promise<MSVCPExpected<int32_t>>(), Args);
145+
ASSERT_THAT_EXPECTED(R4, Succeeded());
146+
EXPECT_EQ(*R4, 0);
147+
}

0 commit comments

Comments
 (0)