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/EPCGenericDylibManagerSPS.h"
13#include "llvm/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManagerSPS.h"
14#include "llvm/ExecutionEngine/Orc/EPCGenericMemoryAccessSPS.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
137void InProcessEPC::callWrapperAsync(ExecutorAddr WrapperFnAddr,
138 IncomingWFRHandler OnComplete,
139 ArrayRef<char> ArgBuffer) {
140 if (C->EnterMessageScope(C)) {
141 auto CallId = registerPendingCallWrapperResult(H: std::move(OnComplete));
142 auto ArgBytes = shared::WrapperFunctionBuffer::copyFrom(Source: ArgBuffer.data(),
143 Size: ArgBuffer.size());
144
145 LLVM_DEBUG(dbgs() << "InProcessEPC: callWrapperAsync call id " << CallId
146 << " to " << WrapperFnAddr << "\n");
147
148 C->CallWrapper(C->IPCA, CallId, WrapperFnAddr.toPtr<void *>(),
149 ArgBytes.release());
150 C->LeaveMessageScope(C);
151 } else
152 OnComplete(shared::WrapperFunctionBuffer::createOutOfBandError(
153 Msg: "connection closed"));
154}
155
156Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>>
157InProcessEPC::createDefaultMemoryManager() {
158 // FIXME: Should actually use InProcessMemoryManager for this.
159 return sps::createEPCGenericJITLinkMemoryManager(ES&: getExecutionSession());
160}
161
162Expected<std::unique_ptr<DylibManager>> InProcessEPC::createDefaultDylibMgr() {
163 // FIXME: Should actually use in-process for this.
164 return sps::createEPCGenericDylibManager(ES&: getExecutionSession());
165}
166
167Expected<std::unique_ptr<MemoryAccess>>
168InProcessEPC::createDefaultMemoryAccess() {
169 // FIXME: Should actually use in-process for this.
170 return sps::createEPCGenericMemoryAccess(ES&: getExecutionSession());
171}
172
173Error InProcessEPC::disconnect() {
174 doDisconnect();
175 return Error::success();
176}
177
178uint64_t InProcessEPC::registerPendingCallWrapperResult(IncomingWFRHandler H) {
179 std::scoped_lock<std::mutex> Lock(M);
180 assert(!PendingCallWrapperResults.count(NextCallId) &&
181 "CallId already in use");
182 PendingCallWrapperResults[NextCallId] = std::move(H);
183 return NextCallId++;
184}
185
186void InProcessEPC::doDisconnect() {
187 // Disconnect from InProcessControllerAccess. This should prevent any further
188 // incoming or outgoing calls.
189 C->Disconnect(C);
190
191 // Drain any pending handlers.
192 DenseMap<uint64_t, IncomingWFRHandler> HandlersToDrain;
193 {
194 std::scoped_lock<std::mutex> Lock(M);
195 HandlersToDrain = std::move(PendingCallWrapperResults);
196 }
197
198 for (auto &[_, H] : HandlersToDrain)
199 H(shared::WrapperFunctionBuffer::createOutOfBandError(Msg: "disconnected"));
200}
201
202void InProcessEPC::callJITDispatch(uint64_t CallId, void *HandlerTag,
203 shared::CWrapperFunctionBuffer ArgBytes) {
204 assert(C->ReturnJITDispatchResult && "ReturnJITDispatchResult not set");
205
206 LLVM_DEBUG(dbgs() << "InProcessEPC: JIT-dispatch call id " << CallId << " to "
207 << HandlerTag << "\n");
208
209 getExecutionSession().runJITDispatchHandler(
210 SendResult: [this, CallId](shared::WrapperFunctionBuffer ResultBytes) {
211 LLVM_DEBUG(dbgs() << "InProcessEPC: Returning JIT-dispatch result for "
212 "call id "
213 << CallId << "\n");
214 if (C->EnterMessageScope(C)) {
215 C->ReturnJITDispatchResult(C->IPCA, CallId, ResultBytes.release());
216 C->LeaveMessageScope(C);
217 }
218 },
219 HandlerFnTagAddr: ExecutorAddr::fromPtr(Ptr: HandlerTag),
220 ArgBytes: shared::WrapperFunctionBuffer(ArgBytes));
221}
222
223void InProcessEPC::callJITDispatchEntry(
224 void *IPEPC, uint64_t CallId, void *HandlerTag,
225 shared::CWrapperFunctionBuffer ArgBytes) {
226 static_cast<InProcessEPC *>(IPEPC)->callJITDispatch(CallId, HandlerTag,
227 ArgBytes);
228}
229
230void InProcessEPC::returnWrapperResult(
231 uint64_t CallId, shared::CWrapperFunctionBuffer ResultBytes) {
232
233 LLVM_DEBUG(dbgs() << "InProcessEPC: Wrapper result for call id " << CallId
234 << "\n");
235
236 IncomingWFRHandler H;
237 {
238 std::scoped_lock<std::mutex> Lock(M);
239 auto I = PendingCallWrapperResults.find(Val: CallId);
240 if (I != PendingCallWrapperResults.end()) {
241 H = std::move(I->second);
242 PendingCallWrapperResults.erase(I);
243 }
244 }
245
246 if (!H) {
247 getExecutionSession().reportError(Err: make_error<StringError>(
248 Args: "InProcessEPC received result for invalid call id " + Twine(CallId),
249 Args: inconvertibleErrorCode()));
250 return;
251 }
252
253 H(shared::WrapperFunctionBuffer(ResultBytes));
254}
255
256void InProcessEPC::returnWrapperResultEntry(
257 void *IPEPC, uint64_t CallId, shared::CWrapperFunctionBuffer ResultBytes) {
258 static_cast<InProcessEPC *>(IPEPC)->returnWrapperResult(CallId, ResultBytes);
259}
260
261} // namespace llvm::orc
262