SimpleRemoteEPC is an ExecutorProcessControl implementation (with corresponding new server class) that uses ORC SimplePackedSerialization (SPS) to serialize and deserialize EPC-messages to/from byte-buffers. The byte-buffers are sent and received via a new SimpleRemoteEPCTransport interface that can be implemented to run SimpleRemoteEPC over whatever underlying transport system (IPC, RPC, network sockets, etc.) best suits your use case. The SimpleRemoteEPCServer class provides executor-side support. It uses a customizable SimpleRemoteEPCServer::Dispatcher object to dispatch wrapper function calls to prevent the RPC thread from being blocked (a problem in some earlier remote-JIT server implementations). Almost all functionality (beyond the bare basics needed to bootstrap) is implemented as wrapper functions to keep the implementation simple and uniform. Compared to previous remote JIT utilities (OrcRemoteTarget*, OrcRPCExecutorProcessControl), more consideration has been given to disconnection and error handling behavior: Graceful disconnection is now always initiated by the ORC side of the connection, and failure at either end (or in the transport) will result in Errors being delivered to both ends to enable controlled tear-down of the JIT and Executor (in the Executor's case this means "as controlled as the JIT'd code allows"). The introduction of SimpleRemoteEPC will allow us to remove other remote-JIT support from ORC (including the legacy OrcRemoteTarget* code used by lli, and the OrcRPCExecutorProcessControl and OrcRPCEPCServer classes), and then remove ORC RPC itself. The llvm-jitlink and llvm-jitlink-executor tools have been updated to use SimpleRemoteEPC over file descriptors. Future commits will move lli and other tools and example code to this system, and remove ORC RPC.
403 lines
15 KiB
C++
403 lines
15 KiB
C++
//===------- 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/Shared/TargetProcessControlTypes.h"
|
|
#include "llvm/ExecutionEngine/Orc/TargetProcess/TargetExecutionUtils.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/Host.h"
|
|
#include "llvm/Support/Process.h"
|
|
|
|
#define DEBUG_TYPE "orc"
|
|
|
|
using namespace llvm::orc::shared;
|
|
|
|
namespace llvm {
|
|
namespace orc {
|
|
|
|
static llvm::orc::shared::detail::CWrapperFunctionResult
|
|
reserveWrapper(const char *ArgData, size_t ArgSize) {
|
|
return WrapperFunction<SPSOrcTargetProcessAllocate>::handle(
|
|
ArgData, ArgSize,
|
|
[](uint64_t Size) -> Expected<ExecutorAddress> {
|
|
std::error_code EC;
|
|
auto MB = sys::Memory::allocateMappedMemory(
|
|
Size, 0, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
|
|
if (EC)
|
|
return errorCodeToError(EC);
|
|
return ExecutorAddress::fromPtr(MB.base());
|
|
})
|
|
.release();
|
|
}
|
|
|
|
static llvm::orc::shared::detail::CWrapperFunctionResult
|
|
finalizeWrapper(const char *ArgData, size_t ArgSize) {
|
|
return WrapperFunction<SPSOrcTargetProcessFinalize>::handle(
|
|
ArgData, ArgSize,
|
|
[](const tpctypes::FinalizeRequest &FR) -> Error {
|
|
for (auto &Seg : FR) {
|
|
char *Mem = Seg.Addr.toPtr<char *>();
|
|
memcpy(Mem, Seg.Content.data(), Seg.Content.size());
|
|
memset(Mem + Seg.Content.size(), 0,
|
|
Seg.Size - Seg.Content.size());
|
|
assert(Seg.Size <= std::numeric_limits<size_t>::max());
|
|
if (auto EC = sys::Memory::protectMappedMemory(
|
|
{Mem, static_cast<size_t>(Seg.Size)},
|
|
tpctypes::fromWireProtectionFlags(Seg.Prot)))
|
|
return errorCodeToError(EC);
|
|
if (Seg.Prot & tpctypes::WPF_Exec)
|
|
sys::Memory::InvalidateInstructionCache(Mem, Seg.Size);
|
|
}
|
|
return Error::success();
|
|
})
|
|
.release();
|
|
}
|
|
|
|
static llvm::orc::shared::detail::CWrapperFunctionResult
|
|
deallocateWrapper(const char *ArgData, size_t ArgSize) {
|
|
return WrapperFunction<SPSOrcTargetProcessDeallocate>::handle(
|
|
ArgData, ArgSize,
|
|
[](ExecutorAddress Base, uint64_t Size) -> Error {
|
|
sys::MemoryBlock MB(Base.toPtr<void *>(), Size);
|
|
if (auto EC = sys::Memory::releaseMappedMemory(MB))
|
|
return errorCodeToError(EC);
|
|
return Error::success();
|
|
})
|
|
.release();
|
|
}
|
|
|
|
template <typename WriteT, typename SPSWriteT>
|
|
static llvm::orc::shared::detail::CWrapperFunctionResult
|
|
writeUIntsWrapper(const char *ArgData, size_t ArgSize) {
|
|
return WrapperFunction<void(SPSSequence<SPSWriteT>)>::handle(
|
|
ArgData, ArgSize,
|
|
[](std::vector<WriteT> Ws) {
|
|
for (auto &W : Ws)
|
|
*jitTargetAddressToPointer<decltype(W.Value) *>(W.Address) =
|
|
W.Value;
|
|
})
|
|
.release();
|
|
}
|
|
|
|
static llvm::orc::shared::detail::CWrapperFunctionResult
|
|
writeBuffersWrapper(const char *ArgData, size_t ArgSize) {
|
|
return WrapperFunction<void(SPSSequence<SPSMemoryAccessBufferWrite>)>::handle(
|
|
ArgData, ArgSize,
|
|
[](std::vector<tpctypes::BufferWrite> Ws) {
|
|
for (auto &W : Ws)
|
|
memcpy(jitTargetAddressToPointer<char *>(W.Address),
|
|
W.Buffer.data(), W.Buffer.size());
|
|
})
|
|
.release();
|
|
}
|
|
|
|
static llvm::orc::shared::detail::CWrapperFunctionResult
|
|
runAsMainWrapper(const char *ArgData, size_t ArgSize) {
|
|
return WrapperFunction<SPSRunAsMainSignature>::handle(
|
|
ArgData, ArgSize,
|
|
[](ExecutorAddress MainAddr,
|
|
std::vector<std::string> Args) -> int64_t {
|
|
return runAsMain(MainAddr.toPtr<int (*)(int, char *[])>(), Args);
|
|
})
|
|
.release();
|
|
}
|
|
|
|
SimpleRemoteEPCServer::Dispatcher::~Dispatcher() {}
|
|
|
|
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; });
|
|
}
|
|
|
|
StringMap<ExecutorAddress> SimpleRemoteEPCServer::defaultBootstrapSymbols() {
|
|
StringMap<ExecutorAddress> DBS;
|
|
|
|
DBS["__llvm_orc_memory_reserve"] = ExecutorAddress::fromPtr(&reserveWrapper);
|
|
DBS["__llvm_orc_memory_finalize"] =
|
|
ExecutorAddress::fromPtr(&finalizeWrapper);
|
|
DBS["__llvm_orc_memory_deallocate"] =
|
|
ExecutorAddress::fromPtr(&deallocateWrapper);
|
|
DBS["__llvm_orc_memory_write_uint8s"] = ExecutorAddress::fromPtr(
|
|
&writeUIntsWrapper<tpctypes::UInt8Write,
|
|
shared::SPSMemoryAccessUInt8Write>);
|
|
DBS["__llvm_orc_memory_write_uint16s"] = ExecutorAddress::fromPtr(
|
|
&writeUIntsWrapper<tpctypes::UInt16Write,
|
|
shared::SPSMemoryAccessUInt16Write>);
|
|
DBS["__llvm_orc_memory_write_uint32s"] = ExecutorAddress::fromPtr(
|
|
&writeUIntsWrapper<tpctypes::UInt32Write,
|
|
shared::SPSMemoryAccessUInt32Write>);
|
|
DBS["__llvm_orc_memory_write_uint64s"] = ExecutorAddress::fromPtr(
|
|
&writeUIntsWrapper<tpctypes::UInt64Write,
|
|
shared::SPSMemoryAccessUInt64Write>);
|
|
DBS["__llvm_orc_memory_write_buffers"] =
|
|
ExecutorAddress::fromPtr(&writeBuffersWrapper);
|
|
DBS["__llvm_orc_run_as_main"] = ExecutorAddress::fromPtr(&runAsMainWrapper);
|
|
DBS["__llvm_orc_load_dylib"] = ExecutorAddress::fromPtr(&loadDylibWrapper);
|
|
DBS["__llvm_orc_lookup_symbols"] =
|
|
ExecutorAddress::fromPtr(&lookupSymbolsWrapper);
|
|
return DBS;
|
|
}
|
|
|
|
Expected<SimpleRemoteEPCTransportClient::HandleMessageAction>
|
|
SimpleRemoteEPCServer::handleMessage(SimpleRemoteEPCOpcode OpC, uint64_t SeqNo,
|
|
ExecutorAddress TagAddr,
|
|
SimpleRemoteEPCArgBytesVector ArgBytes) {
|
|
using UT = std::underlying_type_t<SimpleRemoteEPCOpcode>;
|
|
if (static_cast<UT>(OpC) < static_cast<UT>(SimpleRemoteEPCOpcode::FirstOpC) ||
|
|
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:
|
|
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::WrapperFunctionResult::createOutOfBandError("disconnecting"));
|
|
|
|
// TODO: Free attached resources.
|
|
// 1. Close libraries in DylibHandles.
|
|
|
|
// Wait for dispatcher to clear.
|
|
D->shutdown();
|
|
|
|
std::lock_guard<std::mutex> Lock(ServerStateMutex);
|
|
ShutdownErr = joinErrors(std::move(ShutdownErr), std::move(Err));
|
|
RunState = ServerShutDown;
|
|
ShutdownCV.notify_all();
|
|
}
|
|
|
|
Error SimpleRemoteEPCServer::sendSetupMessage(
|
|
StringMap<ExecutorAddress> BootstrapSymbols) {
|
|
|
|
using namespace SimpleRemoteEPCDefaultBootstrapSymbolNames;
|
|
|
|
std::vector<char> SetupPacket;
|
|
SimpleRemoteEPCExecutorInfo EI;
|
|
EI.TargetTriple = sys::getProcessTriple();
|
|
if (auto PageSize = sys::Process::getPageSize())
|
|
EI.PageSize = *PageSize;
|
|
else
|
|
return PageSize.takeError();
|
|
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] =
|
|
ExecutorAddress::fromPtr(this);
|
|
EI.BootstrapSymbols[DispatchFnName] =
|
|
ExecutorAddress::fromPtr(jitDispatchEntry);
|
|
|
|
using SPSSerialize =
|
|
shared::SPSArgList<shared::SPSSimpleRemoteEPCExecutorInfo>;
|
|
auto SetupPacketBytes =
|
|
shared::WrapperFunctionResult::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 T->sendMessage(SimpleRemoteEPCOpcode::Setup, 0, ExecutorAddress(),
|
|
{SetupPacketBytes.data(), SetupPacketBytes.size()});
|
|
}
|
|
|
|
Error SimpleRemoteEPCServer::handleResult(
|
|
uint64_t SeqNo, ExecutorAddress TagAddr,
|
|
SimpleRemoteEPCArgBytesVector ArgBytes) {
|
|
std::promise<shared::WrapperFunctionResult> *P = nullptr;
|
|
{
|
|
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);
|
|
}
|
|
auto R = shared::WrapperFunctionResult::allocate(ArgBytes.size());
|
|
memcpy(R.data(), ArgBytes.data(), ArgBytes.size());
|
|
P->set_value(std::move(R));
|
|
return Error::success();
|
|
}
|
|
|
|
void SimpleRemoteEPCServer::handleCallWrapper(
|
|
uint64_t RemoteSeqNo, ExecutorAddress TagAddr,
|
|
SimpleRemoteEPCArgBytesVector ArgBytes) {
|
|
D->dispatch([this, RemoteSeqNo, TagAddr, ArgBytes = std::move(ArgBytes)]() {
|
|
using WrapperFnTy =
|
|
shared::detail::CWrapperFunctionResult (*)(const char *, size_t);
|
|
auto *Fn = TagAddr.toPtr<WrapperFnTy>();
|
|
shared::WrapperFunctionResult ResultBytes(
|
|
Fn(ArgBytes.data(), ArgBytes.size()));
|
|
if (auto Err = T->sendMessage(SimpleRemoteEPCOpcode::Result, RemoteSeqNo,
|
|
ExecutorAddress(),
|
|
{ResultBytes.data(), ResultBytes.size()}))
|
|
ReportError(std::move(Err));
|
|
});
|
|
}
|
|
|
|
shared::detail::CWrapperFunctionResult
|
|
SimpleRemoteEPCServer::loadDylibWrapper(const char *ArgData, size_t ArgSize) {
|
|
return shared::WrapperFunction<shared::SPSLoadDylibSignature>::handle(
|
|
ArgData, ArgSize,
|
|
[](ExecutorAddress ExecutorSessionObj, std::string Path,
|
|
uint64_t Flags) -> Expected<uint64_t> {
|
|
return ExecutorSessionObj.toPtr<SimpleRemoteEPCServer *>()
|
|
->loadDylib(Path, Flags);
|
|
})
|
|
.release();
|
|
}
|
|
|
|
shared::detail::CWrapperFunctionResult
|
|
SimpleRemoteEPCServer::lookupSymbolsWrapper(const char *ArgData,
|
|
size_t ArgSize) {
|
|
return shared::WrapperFunction<shared::SPSLookupSymbolsSignature>::handle(
|
|
ArgData, ArgSize,
|
|
[](ExecutorAddress ExecutorSessionObj,
|
|
std::vector<RemoteSymbolLookup> Lookup) {
|
|
return ExecutorSessionObj.toPtr<SimpleRemoteEPCServer *>()
|
|
->lookupSymbols(Lookup);
|
|
})
|
|
.release();
|
|
}
|
|
|
|
Expected<tpctypes::DylibHandle>
|
|
SimpleRemoteEPCServer::loadDylib(const std::string &Path, uint64_t Mode) {
|
|
std::string ErrMsg;
|
|
const char *P = Path.empty() ? nullptr : Path.c_str();
|
|
auto DL = sys::DynamicLibrary::getPermanentLibrary(P, &ErrMsg);
|
|
if (!DL.isValid())
|
|
return make_error<StringError>(std::move(ErrMsg), inconvertibleErrorCode());
|
|
std::lock_guard<std::mutex> Lock(ServerStateMutex);
|
|
uint64_t Id = Dylibs.size();
|
|
Dylibs.push_back(std::move(DL));
|
|
return Id;
|
|
}
|
|
|
|
Expected<std::vector<std::vector<ExecutorAddress>>>
|
|
SimpleRemoteEPCServer::lookupSymbols(const std::vector<RemoteSymbolLookup> &L) {
|
|
std::vector<std::vector<ExecutorAddress>> Result;
|
|
|
|
for (const auto &E : L) {
|
|
if (E.H >= Dylibs.size())
|
|
return make_error<StringError>("Unrecognized handle",
|
|
inconvertibleErrorCode());
|
|
auto &DL = Dylibs[E.H];
|
|
Result.push_back({});
|
|
|
|
for (const auto &Sym : E.Symbols) {
|
|
|
|
const char *DemangledSymName = Sym.Name.c_str();
|
|
#ifdef __APPLE__
|
|
if (*DemangledSymName == '_')
|
|
++DemangledSymName;
|
|
#endif
|
|
|
|
void *Addr = DL.getAddressOfSymbol(DemangledSymName);
|
|
if (!Addr && Sym.Required)
|
|
return make_error<StringError>(Twine("Missing definition for ") +
|
|
DemangledSymName,
|
|
inconvertibleErrorCode());
|
|
|
|
Result.back().push_back(ExecutorAddress::fromPtr(Addr));
|
|
}
|
|
}
|
|
|
|
return std::move(Result);
|
|
}
|
|
|
|
shared::WrapperFunctionResult
|
|
SimpleRemoteEPCServer::doJITDispatch(const void *FnTag, const char *ArgData,
|
|
size_t ArgSize) {
|
|
uint64_t SeqNo;
|
|
std::promise<shared::WrapperFunctionResult> ResultP;
|
|
auto ResultF = ResultP.get_future();
|
|
{
|
|
std::lock_guard<std::mutex> Lock(ServerStateMutex);
|
|
if (RunState != ServerRunning)
|
|
return shared::WrapperFunctionResult::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 =
|
|
T->sendMessage(SimpleRemoteEPCOpcode::CallWrapper, SeqNo,
|
|
ExecutorAddress::fromPtr(FnTag), {ArgData, ArgSize}))
|
|
ReportError(std::move(Err));
|
|
|
|
return ResultF.get();
|
|
}
|
|
|
|
shared::detail::CWrapperFunctionResult
|
|
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
|