| //===------- SimpleEPCServer.cpp - EPC over simple abstract channel -------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/ExecutionEngine/Orc/TargetProcess/SimpleRemoteEPCServer.h" | |
| #include "llvm/ExecutionEngine/Orc/TargetProcess/DefaultHostBootstrapValues.h" | |
| #include "llvm/ExecutionEngine/Orc/TargetProcess/OrcRTBootstrap.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #include "llvm/Support/Process.h" | |
| #include "llvm/TargetParser/Host.h" | |
| #define DEBUG_TYPE "orc" | |
| using namespace llvm::orc::shared; | |
| namespace llvm { | |
| namespace orc { | |
| ExecutorBootstrapService::~ExecutorBootstrapService() = default; | |
| SimpleRemoteEPCServer::Dispatcher::~Dispatcher() = default; | |
| #if LLVM_ENABLE_THREADS | |
| void SimpleRemoteEPCServer::ThreadDispatcher::dispatch( | |
| unique_function<void()> Work) { | |
| { | |
| std::lock_guard<std::mutex> Lock(DispatchMutex); | |
| if (!Running) | |
| return; | |
| ++Outstanding; | |
| } | |
| std::thread([this, Work = std::move(Work)]() mutable { | |
| Work(); | |
| std::lock_guard<std::mutex> Lock(DispatchMutex); | |
| --Outstanding; | |
| OutstandingCV.notify_all(); | |
| }).detach(); | |
| } | |
| void SimpleRemoteEPCServer::ThreadDispatcher::shutdown() { | |
| std::unique_lock<std::mutex> Lock(DispatchMutex); | |
| Running = false; | |
| OutstandingCV.wait(Lock, [this]() { return Outstanding == 0; }); | |
| } | |
| #endif | |
| StringMap<ExecutorAddr> SimpleRemoteEPCServer::defaultBootstrapSymbols() { | |
| StringMap<ExecutorAddr> DBS; | |
| rt_bootstrap::addTo(DBS); | |
| return DBS; | |
| } | |
| Expected<SimpleRemoteEPCTransportClient::HandleMessageAction> | |
| SimpleRemoteEPCServer::handleMessage(SimpleRemoteEPCOpcode OpC, uint64_t SeqNo, | |
| ExecutorAddr TagAddr, | |
| shared::WrapperFunctionBuffer ArgBytes) { | |
| LLVM_DEBUG({ | |
| dbgs() << "SimpleRemoteEPCServer::handleMessage: opc = "; | |
| switch (OpC) { | |
| case SimpleRemoteEPCOpcode::Setup: | |
| dbgs() << "Setup"; | |
| assert(SeqNo == 0 && "Non-zero SeqNo for Setup?"); | |
| assert(!TagAddr && "Non-zero TagAddr for Setup?"); | |
| break; | |
| case SimpleRemoteEPCOpcode::Hangup: | |
| dbgs() << "Hangup"; | |
| assert(SeqNo == 0 && "Non-zero SeqNo for Hangup?"); | |
| assert(!TagAddr && "Non-zero TagAddr for Hangup?"); | |
| break; | |
| case SimpleRemoteEPCOpcode::Result: | |
| dbgs() << "Result"; | |
| break; | |
| case SimpleRemoteEPCOpcode::CallWrapper: | |
| dbgs() << "CallWrapper"; | |
| break; | |
| } | |
| dbgs() << ", seqno = " << SeqNo << ", tag-addr = " << TagAddr | |
| << ", arg-buffer = " << formatv("{0:x}", ArgBytes.size()) | |
| << " bytes\n"; | |
| }); | |
| using UT = std::underlying_type_t<SimpleRemoteEPCOpcode>; | |
| if (static_cast<UT>(OpC) > static_cast<UT>(SimpleRemoteEPCOpcode::LastOpC)) | |
| return make_error<StringError>("Unexpected opcode", | |
| inconvertibleErrorCode()); | |
| // TODO: Clean detach message? | |
| switch (OpC) { | |
| case SimpleRemoteEPCOpcode::Setup: | |
| return make_error<StringError>("Unexpected Setup opcode", | |
| inconvertibleErrorCode()); | |
| case SimpleRemoteEPCOpcode::Hangup: { | |
| { | |
| std::lock_guard<std::mutex> Lock(ServerStateMutex); | |
| RemoteHangup = true; | |
| } | |
| if (auto Err = decodeHangupPayload(std::move(ArgBytes))) | |
| return std::move(Err); | |
| return SimpleRemoteEPCTransportClient::EndSession; | |
| } | |
| case SimpleRemoteEPCOpcode::Result: | |
| if (auto Err = handleResult(SeqNo, TagAddr, std::move(ArgBytes))) | |
| return std::move(Err); | |
| break; | |
| case SimpleRemoteEPCOpcode::CallWrapper: | |
| handleCallWrapper(SeqNo, TagAddr, std::move(ArgBytes)); | |
| break; | |
| } | |
| return ContinueSession; | |
| } | |
| Error SimpleRemoteEPCServer::waitForDisconnect() { | |
| std::unique_lock<std::mutex> Lock(ServerStateMutex); | |
| ShutdownCV.wait(Lock, [this]() { return RunState == ServerShutDown; }); | |
| return std::move(ShutdownErr); | |
| } | |
| void SimpleRemoteEPCServer::handleDisconnect(Error Err) { | |
| PendingJITDispatchResultsMap TmpPending; | |
| { | |
| std::lock_guard<std::mutex> Lock(ServerStateMutex); | |
| std::swap(TmpPending, PendingJITDispatchResults); | |
| RunState = ServerShuttingDown; | |
| } | |
| // Send out-of-band errors to any waiting threads. | |
| for (auto &KV : TmpPending) | |
| KV.second->set_value( | |
| shared::WrapperFunctionBuffer::createOutOfBandError("disconnecting")); | |
| // Wait for dispatcher to clear. | |
| D->shutdown(); | |
| // Shut down services. | |
| while (!Services.empty()) { | |
| ShutdownErr = | |
| joinErrors(std::move(ShutdownErr), Services.back()->shutdown()); | |
| Services.pop_back(); | |
| } | |
| std::lock_guard<std::mutex> Lock(ServerStateMutex); | |
| // The server never initiates a disconnection, so if the transport reported no | |
| // error and no hangup arrived then the controller went away without telling | |
| // us. The cause is not knowable from here -- it may have crashed, been | |
| // killed, or become unreachable -- so report what was observed rather than a | |
| // cause. | |
| // | |
| // A missing hangup is evidence, not proof: a hangup can also be lost in | |
| // transit, since closing a TCP socket with unread data queued sends an RST, | |
| // which can discard bytes the peer had already delivered. We accept that | |
| // rather than draining the read side before closing -- the cost is a | |
| // misleading diagnostic on a session that is ending regardless, whereas a | |
| // drain risks stalling teardown on a peer that never closes. | |
| Error DisconnectReason = | |
| (!Err && !RemoteHangup) | |
| ? make_error<StringError>("Connection closed without hangup", | |
| inconvertibleErrorCode()) | |
| : std::move(Err); | |
| ShutdownErr = joinErrors(std::move(ShutdownErr), std::move(DisconnectReason)); | |
| RunState = ServerShutDown; | |
| ShutdownCV.notify_all(); | |
| } | |
| Error SimpleRemoteEPCServer::sendMessage(SimpleRemoteEPCOpcode OpC, | |
| uint64_t SeqNo, ExecutorAddr TagAddr, | |
| ArrayRef<char> ArgBytes) { | |
| LLVM_DEBUG({ | |
| dbgs() << "SimpleRemoteEPCServer::sendMessage: opc = "; | |
| switch (OpC) { | |
| case SimpleRemoteEPCOpcode::Setup: | |
| dbgs() << "Setup"; | |
| assert(SeqNo == 0 && "Non-zero SeqNo for Setup?"); | |
| assert(!TagAddr && "Non-zero TagAddr for Setup?"); | |
| break; | |
| case SimpleRemoteEPCOpcode::Hangup: | |
| dbgs() << "Hangup"; | |
| assert(SeqNo == 0 && "Non-zero SeqNo for Hangup?"); | |
| assert(!TagAddr && "Non-zero TagAddr for Hangup?"); | |
| break; | |
| case SimpleRemoteEPCOpcode::Result: | |
| dbgs() << "Result"; | |
| break; | |
| case SimpleRemoteEPCOpcode::CallWrapper: | |
| dbgs() << "CallWrapper"; | |
| break; | |
| } | |
| dbgs() << ", seqno = " << SeqNo << ", tag-addr = " << TagAddr | |
| << ", arg-buffer = " << formatv("{0:x}", ArgBytes.size()) | |
| << " bytes\n"; | |
| }); | |
| auto Err = T->sendMessage(OpC, SeqNo, TagAddr, ArgBytes); | |
| LLVM_DEBUG({ | |
| if (Err) | |
| dbgs() << " \\--> SimpleRemoteEPC::sendMessage failed\n"; | |
| }); | |
| return Err; | |
| } | |
| Error SimpleRemoteEPCServer::sendSetupMessage( | |
| StringMap<std::vector<char>> BootstrapMap, | |
| StringMap<ExecutorAddr> BootstrapSymbols) { | |
| using namespace SimpleRemoteEPCDefaultBootstrapSymbolNames; | |
| SimpleRemoteEPCExecutorInfo EI; | |
| EI.TargetTriple = sys::getProcessTriple(); | |
| if (auto PageSize = sys::Process::getPageSize()) | |
| EI.PageSize = *PageSize; | |
| else | |
| return PageSize.takeError(); | |
| EI.BootstrapMap = std::move(BootstrapMap); | |
| EI.BootstrapSymbols = std::move(BootstrapSymbols); | |
| assert(!EI.BootstrapSymbols.count(ExecutorSessionObjectName) && | |
| "Dispatch context name should not be set"); | |
| assert(!EI.BootstrapSymbols.count(DispatchFnName) && | |
| "Dispatch function name should not be set"); | |
| EI.BootstrapSymbols[ExecutorSessionObjectName] = ExecutorAddr::fromPtr(this); | |
| EI.BootstrapSymbols[DispatchFnName] = ExecutorAddr::fromPtr(jitDispatchEntry); | |
| addDefaultBootstrapValuesForHostProcess(EI.BootstrapMap, EI.BootstrapSymbols); | |
| using SPSSerialize = | |
| shared::SPSArgList<shared::SPSSimpleRemoteEPCExecutorInfo>; | |
| auto SetupPacketBytes = | |
| shared::WrapperFunctionBuffer::allocate(SPSSerialize::size(EI)); | |
| shared::SPSOutputBuffer OB(SetupPacketBytes.data(), SetupPacketBytes.size()); | |
| if (!SPSSerialize::serialize(OB, EI)) | |
| return make_error<StringError>("Could not send setup packet", | |
| inconvertibleErrorCode()); | |
| return sendMessage(SimpleRemoteEPCOpcode::Setup, 0, ExecutorAddr(), | |
| {SetupPacketBytes.data(), SetupPacketBytes.size()}); | |
| } | |
| Error SimpleRemoteEPCServer::handleResult( | |
| uint64_t SeqNo, ExecutorAddr TagAddr, | |
| shared::WrapperFunctionBuffer ArgBytes) { | |
| std::promise<shared::WrapperFunctionBuffer> *P = nullptr; | |
| auto R = decodeResultMessage(TagAddr, std::move(ArgBytes)); | |
| if (!R) | |
| return R.takeError(); | |
| { | |
| std::lock_guard<std::mutex> Lock(ServerStateMutex); | |
| auto I = PendingJITDispatchResults.find(SeqNo); | |
| if (I == PendingJITDispatchResults.end()) | |
| return make_error<StringError>("No call for sequence number " + | |
| Twine(SeqNo), | |
| inconvertibleErrorCode()); | |
| P = I->second; | |
| PendingJITDispatchResults.erase(I); | |
| releaseSeqNo(SeqNo); | |
| } | |
| P->set_value(std::move(*R)); | |
| return Error::success(); | |
| } | |
| void SimpleRemoteEPCServer::handleCallWrapper( | |
| uint64_t RemoteSeqNo, ExecutorAddr TagAddr, | |
| shared::WrapperFunctionBuffer ArgBytes) { | |
| D->dispatch([this, RemoteSeqNo, TagAddr, ArgBytes = std::move(ArgBytes)]() { | |
| using WrapperFnTy = | |
| shared::CWrapperFunctionBuffer (*)(const char *, size_t); | |
| auto *Fn = TagAddr.toPtr<WrapperFnTy>(); | |
| shared::WrapperFunctionBuffer ResultBytes( | |
| Fn(ArgBytes.data(), ArgBytes.size())); | |
| auto [ResultTag, Payload] = encodeResultMessage(std::move(ResultBytes)); | |
| if (auto Err = sendMessage(SimpleRemoteEPCOpcode::Result, RemoteSeqNo, | |
| ResultTag, {Payload.data(), Payload.size()})) | |
| ReportError(std::move(Err)); | |
| }); | |
| } | |
| shared::WrapperFunctionBuffer | |
| SimpleRemoteEPCServer::doJITDispatch(const void *FnTag, const char *ArgData, | |
| size_t ArgSize) { | |
| uint64_t SeqNo; | |
| std::promise<shared::WrapperFunctionBuffer> ResultP; | |
| auto ResultF = ResultP.get_future(); | |
| { | |
| std::lock_guard<std::mutex> Lock(ServerStateMutex); | |
| if (RunState != ServerRunning) | |
| return shared::WrapperFunctionBuffer::createOutOfBandError( | |
| "jit_dispatch not available (EPC server shut down)"); | |
| SeqNo = getNextSeqNo(); | |
| assert(!PendingJITDispatchResults.count(SeqNo) && "SeqNo already in use"); | |
| PendingJITDispatchResults[SeqNo] = &ResultP; | |
| } | |
| if (auto Err = sendMessage(SimpleRemoteEPCOpcode::CallWrapper, SeqNo, | |
| ExecutorAddr::fromPtr(FnTag), {ArgData, ArgSize})) | |
| ReportError(std::move(Err)); | |
| return ResultF.get(); | |
| } | |
| shared::CWrapperFunctionBuffer | |
| SimpleRemoteEPCServer::jitDispatchEntry(void *DispatchCtx, const void *FnTag, | |
| const char *ArgData, size_t ArgSize) { | |
| return reinterpret_cast<SimpleRemoteEPCServer *>(DispatchCtx) | |
| ->doJITDispatch(FnTag, ArgData, ArgSize) | |
| .release(); | |
| } | |
| } // end namespace orc | |
| } // end namespace llvm |