1//===---------- InProcessEPC.cpp -- In-process EPC for new ORC runtime ----===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "llvm/ExecutionEngine/Orc/InProcessEPC.h"
10
11#include "llvm/ExecutionEngine/Orc/Core.h"
12#include "llvm/ExecutionEngine/Orc/EPCGenericDylibManager.h"
13#include "llvm/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManager.h"
14#include "llvm/ExecutionEngine/Orc/EPCGenericMemoryAccess.h"
15#include "llvm/ExecutionEngine/Orc/TargetProcess/TargetExecutionUtils.h"
16#include "llvm/Support/DynamicLibrary.h"
17#include "llvm/Support/Process.h"
18
19#define DEBUG_TYPE "orc"
20
21namespace llvm::orc {
22
23Expected<std::unique_ptr<InProcessEPC>>
24InProcessEPC::Create(Connection *C, BootstrapInfoAccess *BIA,
25 std::shared_ptr<SymbolStringPool> SSP,
26 std::unique_ptr<TaskDispatcher> D) {
27 assert(C && "C must not be null");
28 assert(BIA && "BIA must not be null");
29
30 // Lifecycle and IPCA-side fields must be populated by the controller side
31 // before OnConnect is invoked.
32 assert(C->Retain && "C->Retain not set by controller");
33 assert(C->Release && "C->Release not set by controller");
34 assert(C->Disconnect && "C->Disconnect not set by controller");
35 assert(C->EnterMessageScope && "C->EnterMessageScope not set by controller");
36 assert(C->LeaveMessageScope && "C->LeaveMessageScope not set by controller");
37 assert(C->IPCA && "C->IPCA not set by controller");
38 assert(C->CallWrapper && "C->CallWrapper not set by controller");
39 assert(C->ReturnJITDispatchResult &&
40 "C->ReturnJITDispatchResult not set by controller");
41
42 if (!SSP)
43 SSP = std::make_shared<SymbolStringPool>();
44
45 if (!D)
46 D = std::make_unique<InPlaceTaskDispatcher>();
47
48 std::unique_ptr<InProcessEPC> IPEPC(
49 new InProcessEPC(C, std::move(SSP), std::move(D)));
50
51 // First set values in C.
52 C->IPEPC = IPEPC.get();
53 C->CallJITDispatch = callJITDispatchEntry;
54 C->ReturnWrapperResult = returnWrapperResultEntry;
55
56 // Then grab bootstrap values.
57 if (auto PageSize = BIA->GetPageSize(BIA))
58 IPEPC->PageSize = PageSize;
59 else
60 return make_error<StringError>(
61 Args: "Cannot create InProcessEPC with page-size = 0",
62 Args: inconvertibleErrorCode());
63
64 if (auto TT = BIA->GetTargetTriple(BIA)) {
65 IPEPC->TargetTriple = llvm::Triple(TT);
66 } else
67 return make_error<StringError>(
68 Args: "Cannot create InProcessEPC with target-triple = \"\"",
69 Args: inconvertibleErrorCode());
70
71 {
72 const char *Name;
73 const char *ValBytes;
74 uint64_t ValSize;
75 int RC;
76 while ((RC = BIA->GetNextValue(BIA, &Name, &ValBytes, &ValSize)) == 1) {
77 if (!IPEPC->BootstrapMap
78 .try_emplace(Key: Name,
79 Args: std::vector<char>(ValBytes, ValBytes + ValSize))
80 .second)
81 return make_error<StringError>(
82 Args: ("Cannot create InProcessEPC: bootstrap-value map contains "
83 "duplicate key \"" +
84 StringRef(Name) + "\""),
85 Args: inconvertibleErrorCode());
86 }
87 if (RC < 0)
88 return make_error<StringError>(
89 Args: "Cannot create InProcessEPC: bootstrap-value map corrupted",
90 Args: inconvertibleErrorCode());
91 }
92
93 {
94 const char *SymName;
95 uint64_t SymAddr;
96 int RC;
97 while ((RC = BIA->GetNextSymbol(BIA, &SymName, &SymAddr)) == 1) {
98 if (!IPEPC->BootstrapSymbols.try_emplace(Key: SymName, Args: ExecutorAddr(SymAddr))
99 .second)
100 return make_error<StringError>(
101 Args: ("Cannot create InProcessEPC: bootstrap-symbol map contains "
102 "duplicate symbol \"" +
103 StringRef(SymName) + "\""),
104 Args: inconvertibleErrorCode());
105 }
106 if (RC < 0)
107 return make_error<StringError>(
108 Args: "Cannot create InProcessEPC: bootstrap-symbol map corrupted",
109 Args: inconvertibleErrorCode());
110 }
111
112 return std::move(IPEPC);
113}
114
115InProcessEPC::~InProcessEPC() {
116 // Guarantee that a discarded InProcessEPC initiates disconnect, even if it
117 // was never attached to an ExecutionSession (e.g. Create failed partway
118 // through, or the caller dropped the returned object without handing it to
119 // a session). When the InProcessEPC *is* attached, the ExecutionSession is
120 // guaranteed to call disconnect() during shutdown, and this call becomes a
121 // no-op via the idempotency of C->Disconnect.
122 doDisconnect();
123
124 // Shut down the dispatcher.
125 D->shutdown();
126
127 // Release the connection object.
128 C->Release(C);
129}
130
131Expected<int32_t> InProcessEPC::runAsMain(ExecutorAddr MainFnAddr,
132 ArrayRef<std::string> Args) {
133 using MainTy = int (*)(int, char *[]);
134 return orc::runAsMain(Main: MainFnAddr.toPtr<MainTy>(), Args);
135}
136
137Expected<int32_t> InProcessEPC::runAsVoidFunction(ExecutorAddr VoidFnAddr) {
138 using VoidTy = int (*)();
139 return orc::runAsVoidFunction(Func: VoidFnAddr.toPtr<VoidTy>());
140}
141
142Expected<int32_t> InProcessEPC::runAsIntFunction(ExecutorAddr IntFnAddr,
143 int Arg) {
144 using IntTy = int (*)(int);
145 return orc::runAsIntFunction(Func: IntFnAddr.toPtr<IntTy>(), Arg);
146}
147
148void InProcessEPC::callWrapperAsync(ExecutorAddr WrapperFnAddr,
149 IncomingWFRHandler OnComplete,
150 ArrayRef<char> ArgBuffer) {
151 if (C->EnterMessageScope(C)) {
152 auto CallId = registerPendingCallWrapperResult(H: std::move(OnComplete));
153 auto ArgBytes = shared::WrapperFunctionBuffer::copyFrom(Source: ArgBuffer.data(),
154 Size: ArgBuffer.size());
155
156 LLVM_DEBUG(dbgs() << "InProcessEPC: callWrapperAsync call id " << CallId
157 << " to " << WrapperFnAddr << "\n");
158
159 C->CallWrapper(C->IPCA, CallId, WrapperFnAddr.toPtr<void *>(),
160 ArgBytes.release());
161 C->LeaveMessageScope(C);
162 } else
163 OnComplete(shared::WrapperFunctionBuffer::createOutOfBandError(
164 Msg: "connection closed"));
165}
166
167Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>>
168InProcessEPC::createDefaultMemoryManager() {
169 // FIXME: Should actually use InProcessMemoryManager for this.
170 return EPCGenericJITLinkMemoryManager::Create(ES&: getExecutionSession());
171}
172
173Expected<std::unique_ptr<DylibManager>> InProcessEPC::createDefaultDylibMgr() {
174 // FIXME: Should actually use in-process for this.
175 auto DM = EPCGenericDylibManager::Create(ES&: getExecutionSession());
176 if (!DM)
177 return DM.takeError();
178 return std::make_unique<EPCGenericDylibManager>(args: std::move(*DM));
179}
180
181Expected<std::unique_ptr<MemoryAccess>>
182InProcessEPC::createDefaultMemoryAccess() {
183 // FIXME: Should actually use in-process for this.
184 EPCGenericMemoryAccess::FuncAddrs FAs;
185 if (auto Err = getBootstrapSymbols(
186 Pairs: {{FAs.WriteUInt8s, rt::MemoryWriteUInt8sWrapperName},
187 {FAs.WriteUInt16s, rt::MemoryWriteUInt16sWrapperName},
188 {FAs.WriteUInt32s, rt::MemoryWriteUInt32sWrapperName},
189 {FAs.WriteUInt64s, rt::MemoryWriteUInt64sWrapperName},
190 {FAs.WriteBuffers, rt::MemoryWriteBuffersWrapperName},
191 {FAs.WritePointers, rt::MemoryWritePointersWrapperName},
192 {FAs.ReadUInt8s, rt::MemoryReadUInt8sWrapperName},
193 {FAs.ReadUInt16s, rt::MemoryReadUInt16sWrapperName},
194 {FAs.ReadUInt32s, rt::MemoryReadUInt32sWrapperName},
195 {FAs.ReadUInt64s, rt::MemoryReadUInt64sWrapperName},
196 {FAs.ReadBuffers, rt::MemoryReadBuffersWrapperName},
197 {FAs.ReadStrings, rt::MemoryReadStringsWrapperName}}))
198 return std::move(Err);
199
200 return std::make_unique<EPCGenericMemoryAccess>(args&: *this, args&: FAs);
201}
202
203Error InProcessEPC::disconnect() {
204 doDisconnect();
205 return Error::success();
206}
207
208uint64_t InProcessEPC::registerPendingCallWrapperResult(IncomingWFRHandler H) {
209 std::scoped_lock<std::mutex> Lock(M);
210 assert(!PendingCallWrapperResults.count(NextCallId) &&
211 "CallId already in use");
212 PendingCallWrapperResults[NextCallId] = std::move(H);
213 return NextCallId++;
214}
215
216void InProcessEPC::doDisconnect() {
217 // Disconnect from InProcessControllerAccess. This should prevent any further
218 // incoming or outgoing calls.
219 C->Disconnect(C);
220
221 // Drain any pending handlers.
222 DenseMap<uint64_t, IncomingWFRHandler> HandlersToDrain;
223 {
224 std::scoped_lock<std::mutex> Lock(M);
225 HandlersToDrain = std::move(PendingCallWrapperResults);
226 }
227
228 for (auto &[_, H] : HandlersToDrain)
229 H(shared::WrapperFunctionBuffer::createOutOfBandError(Msg: "disconnected"));
230}
231
232void InProcessEPC::callJITDispatch(uint64_t CallId, void *HandlerTag,
233 shared::CWrapperFunctionBuffer ArgBytes) {
234 assert(C->ReturnJITDispatchResult && "ReturnJITDispatchResult not set");
235
236 LLVM_DEBUG(dbgs() << "InProcessEPC: JIT-dispatch call id " << CallId << " to "
237 << HandlerTag << "\n");
238
239 getExecutionSession().runJITDispatchHandler(
240 SendResult: [this, CallId](shared::WrapperFunctionBuffer ResultBytes) {
241 LLVM_DEBUG(dbgs() << "InProcessEPC: Returning JIT-dispatch result for "
242 "call id "
243 << CallId << "\n");
244 if (C->EnterMessageScope(C)) {
245 C->ReturnJITDispatchResult(C->IPCA, CallId, ResultBytes.release());
246 C->LeaveMessageScope(C);
247 }
248 },
249 HandlerFnTagAddr: ExecutorAddr::fromPtr(Ptr: HandlerTag),
250 ArgBytes: shared::WrapperFunctionBuffer(ArgBytes));
251}
252
253void InProcessEPC::callJITDispatchEntry(
254 void *IPEPC, uint64_t CallId, void *HandlerTag,
255 shared::CWrapperFunctionBuffer ArgBytes) {
256 static_cast<InProcessEPC *>(IPEPC)->callJITDispatch(CallId, HandlerTag,
257 ArgBytes);
258}
259
260void InProcessEPC::returnWrapperResult(
261 uint64_t CallId, shared::CWrapperFunctionBuffer ResultBytes) {
262
263 LLVM_DEBUG(dbgs() << "InProcessEPC: Wrapper result for call id " << CallId
264 << "\n");
265
266 IncomingWFRHandler H;
267 {
268 std::scoped_lock<std::mutex> Lock(M);
269 auto I = PendingCallWrapperResults.find(Val: CallId);
270 if (I != PendingCallWrapperResults.end()) {
271 H = std::move(I->second);
272 PendingCallWrapperResults.erase(I);
273 }
274 }
275
276 if (!H) {
277 getExecutionSession().reportError(Err: make_error<StringError>(
278 Args: "InProcessEPC received result for invalid call id " + Twine(CallId),
279 Args: inconvertibleErrorCode()));
280 return;
281 }
282
283 H(shared::WrapperFunctionBuffer(ResultBytes));
284}
285
286void InProcessEPC::returnWrapperResultEntry(
287 void *IPEPC, uint64_t CallId, shared::CWrapperFunctionBuffer ResultBytes) {
288 static_cast<InProcessEPC *>(IPEPC)->returnWrapperResult(CallId, ResultBytes);
289}
290
291} // namespace llvm::orc
292