| 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 | |
| 21 | namespace llvm::orc { |
| 22 | |
| 23 | Expected<std::unique_ptr<InProcessEPC>> |
| 24 | InProcessEPC::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 | |
| 115 | InProcessEPC::~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 | |
| 131 | Expected<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 | |
| 137 | Expected<int32_t> InProcessEPC::runAsVoidFunction(ExecutorAddr VoidFnAddr) { |
| 138 | using VoidTy = int (*)(); |
| 139 | return orc::runAsVoidFunction(Func: VoidFnAddr.toPtr<VoidTy>()); |
| 140 | } |
| 141 | |
| 142 | Expected<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 | |
| 148 | void 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 | |
| 167 | Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>> |
| 168 | InProcessEPC::createDefaultMemoryManager() { |
| 169 | // FIXME: Should actually use InProcessMemoryManager for this. |
| 170 | return EPCGenericJITLinkMemoryManager::Create(ES&: getExecutionSession()); |
| 171 | } |
| 172 | |
| 173 | Expected<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 | |
| 181 | Expected<std::unique_ptr<MemoryAccess>> |
| 182 | InProcessEPC::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 | |
| 203 | Error InProcessEPC::disconnect() { |
| 204 | doDisconnect(); |
| 205 | return Error::success(); |
| 206 | } |
| 207 | |
| 208 | uint64_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 | |
| 216 | void 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 | |
| 232 | void 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 | |
| 253 | void 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 | |
| 260 | void 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 | |
| 286 | void 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 | |