blob: 70fa12138b88d7275830bda9a33670aaafc6e1f4 [file] [edit]
//===---------- InProcessEPC.cpp -- In-process EPC for new ORC runtime ----===//
//
// 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/InProcessEPC.h"
#include "llvm/ExecutionEngine/Orc/Core.h"
#include "llvm/ExecutionEngine/Orc/EPCGenericDylibManager.h"
#include "llvm/ExecutionEngine/Orc/EPCGenericJITLinkMemoryManager.h"
#include "llvm/ExecutionEngine/Orc/EPCGenericMemoryAccess.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/TargetExecutionUtils.h"
#include "llvm/Support/DynamicLibrary.h"
#include "llvm/Support/Process.h"
#define DEBUG_TYPE "orc"
namespace llvm::orc {
Expected<std::unique_ptr<InProcessEPC>>
InProcessEPC::Create(Connection *C, BootstrapInfoAccess *BIA,
std::shared_ptr<SymbolStringPool> SSP,
std::unique_ptr<TaskDispatcher> D) {
assert(C && "C must not be null");
assert(BIA && "BIA must not be null");
// Lifecycle and IPCA-side fields must be populated by the controller side
// before OnConnect is invoked.
assert(C->Retain && "C->Retain not set by controller");
assert(C->Release && "C->Release not set by controller");
assert(C->Disconnect && "C->Disconnect not set by controller");
assert(C->EnterMessageScope && "C->EnterMessageScope not set by controller");
assert(C->LeaveMessageScope && "C->LeaveMessageScope not set by controller");
assert(C->IPCA && "C->IPCA not set by controller");
assert(C->CallWrapper && "C->CallWrapper not set by controller");
assert(C->ReturnJITDispatchResult &&
"C->ReturnJITDispatchResult not set by controller");
if (!SSP)
SSP = std::make_shared<SymbolStringPool>();
if (!D)
D = std::make_unique<InPlaceTaskDispatcher>();
std::unique_ptr<InProcessEPC> IPEPC(
new InProcessEPC(C, std::move(SSP), std::move(D)));
// First set values in C.
C->IPEPC = IPEPC.get();
C->CallJITDispatch = callJITDispatchEntry;
C->ReturnWrapperResult = returnWrapperResultEntry;
// Then grab bootstrap values.
if (auto PageSize = BIA->GetPageSize(BIA))
IPEPC->PageSize = PageSize;
else
return make_error<StringError>(
"Cannot create InProcessEPC with page-size = 0",
inconvertibleErrorCode());
if (auto TT = BIA->GetTargetTriple(BIA)) {
IPEPC->TargetTriple = llvm::Triple(TT);
} else
return make_error<StringError>(
"Cannot create InProcessEPC with target-triple = \"\"",
inconvertibleErrorCode());
{
const char *Name;
const char *ValBytes;
uint64_t ValSize;
int RC;
while ((RC = BIA->GetNextValue(BIA, &Name, &ValBytes, &ValSize)) == 1) {
if (!IPEPC->BootstrapMap
.try_emplace(Name,
std::vector<char>(ValBytes, ValBytes + ValSize))
.second)
return make_error<StringError>(
("Cannot create InProcessEPC: bootstrap-value map contains "
"duplicate key \"" +
StringRef(Name) + "\""),
inconvertibleErrorCode());
}
if (RC < 0)
return make_error<StringError>(
"Cannot create InProcessEPC: bootstrap-value map corrupted",
inconvertibleErrorCode());
}
{
const char *SymName;
uint64_t SymAddr;
int RC;
while ((RC = BIA->GetNextSymbol(BIA, &SymName, &SymAddr)) == 1) {
if (!IPEPC->BootstrapSymbols.try_emplace(SymName, ExecutorAddr(SymAddr))
.second)
return make_error<StringError>(
("Cannot create InProcessEPC: bootstrap-symbol map contains "
"duplicate symbol \"" +
StringRef(SymName) + "\""),
inconvertibleErrorCode());
}
if (RC < 0)
return make_error<StringError>(
"Cannot create InProcessEPC: bootstrap-symbol map corrupted",
inconvertibleErrorCode());
}
return std::move(IPEPC);
}
InProcessEPC::~InProcessEPC() {
// Guarantee that a discarded InProcessEPC initiates disconnect, even if it
// was never attached to an ExecutionSession (e.g. Create failed partway
// through, or the caller dropped the returned object without handing it to
// a session). When the InProcessEPC *is* attached, the ExecutionSession is
// guaranteed to call disconnect() during shutdown, and this call becomes a
// no-op via the idempotency of C->Disconnect.
doDisconnect();
// Shut down the dispatcher.
D->shutdown();
// Release the connection object.
C->Release(C);
}
Expected<int32_t> InProcessEPC::runAsMain(ExecutorAddr MainFnAddr,
ArrayRef<std::string> Args) {
using MainTy = int (*)(int, char *[]);
return orc::runAsMain(MainFnAddr.toPtr<MainTy>(), Args);
}
Expected<int32_t> InProcessEPC::runAsVoidFunction(ExecutorAddr VoidFnAddr) {
using VoidTy = int (*)();
return orc::runAsVoidFunction(VoidFnAddr.toPtr<VoidTy>());
}
Expected<int32_t> InProcessEPC::runAsIntFunction(ExecutorAddr IntFnAddr,
int Arg) {
using IntTy = int (*)(int);
return orc::runAsIntFunction(IntFnAddr.toPtr<IntTy>(), Arg);
}
void InProcessEPC::callWrapperAsync(ExecutorAddr WrapperFnAddr,
IncomingWFRHandler OnComplete,
ArrayRef<char> ArgBuffer) {
if (C->EnterMessageScope(C)) {
auto CallId = registerPendingCallWrapperResult(std::move(OnComplete));
auto ArgBytes = shared::WrapperFunctionBuffer::copyFrom(ArgBuffer.data(),
ArgBuffer.size());
LLVM_DEBUG(dbgs() << "InProcessEPC: callWrapperAsync call id " << CallId
<< " to " << WrapperFnAddr << "\n");
C->CallWrapper(C->IPCA, CallId, WrapperFnAddr.toPtr<void *>(),
ArgBytes.release());
C->LeaveMessageScope(C);
} else
OnComplete(shared::WrapperFunctionBuffer::createOutOfBandError(
"connection closed"));
}
Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>>
InProcessEPC::createDefaultMemoryManager() {
// FIXME: Should actually use InProcessMemoryManager for this.
return EPCGenericJITLinkMemoryManager::Create(getExecutionSession());
}
Expected<std::unique_ptr<DylibManager>> InProcessEPC::createDefaultDylibMgr() {
// FIXME: Should actually use in-process for this.
auto DM = EPCGenericDylibManager::Create(getExecutionSession());
if (!DM)
return DM.takeError();
return std::make_unique<EPCGenericDylibManager>(std::move(*DM));
}
Expected<std::unique_ptr<MemoryAccess>>
InProcessEPC::createDefaultMemoryAccess() {
// FIXME: Should actually use in-process for this.
EPCGenericMemoryAccess::FuncAddrs FAs;
if (auto Err = getBootstrapSymbols(
{{FAs.WriteUInt8s, rt::MemoryWriteUInt8sWrapperName},
{FAs.WriteUInt16s, rt::MemoryWriteUInt16sWrapperName},
{FAs.WriteUInt32s, rt::MemoryWriteUInt32sWrapperName},
{FAs.WriteUInt64s, rt::MemoryWriteUInt64sWrapperName},
{FAs.WriteBuffers, rt::MemoryWriteBuffersWrapperName},
{FAs.WritePointers, rt::MemoryWritePointersWrapperName},
{FAs.ReadUInt8s, rt::MemoryReadUInt8sWrapperName},
{FAs.ReadUInt16s, rt::MemoryReadUInt16sWrapperName},
{FAs.ReadUInt32s, rt::MemoryReadUInt32sWrapperName},
{FAs.ReadUInt64s, rt::MemoryReadUInt64sWrapperName},
{FAs.ReadBuffers, rt::MemoryReadBuffersWrapperName},
{FAs.ReadStrings, rt::MemoryReadStringsWrapperName}}))
return std::move(Err);
return std::make_unique<EPCGenericMemoryAccess>(*this, FAs);
}
Error InProcessEPC::disconnect() {
doDisconnect();
return Error::success();
}
uint64_t InProcessEPC::registerPendingCallWrapperResult(IncomingWFRHandler H) {
std::scoped_lock<std::mutex> Lock(M);
assert(!PendingCallWrapperResults.count(NextCallId) &&
"CallId already in use");
PendingCallWrapperResults[NextCallId] = std::move(H);
return NextCallId++;
}
void InProcessEPC::doDisconnect() {
// Disconnect from InProcessControllerAccess. This should prevent any further
// incoming or outgoing calls.
C->Disconnect(C);
// Drain any pending handlers.
DenseMap<uint64_t, IncomingWFRHandler> HandlersToDrain;
{
std::scoped_lock<std::mutex> Lock(M);
HandlersToDrain = std::move(PendingCallWrapperResults);
}
for (auto &[_, H] : HandlersToDrain)
H(shared::WrapperFunctionBuffer::createOutOfBandError("disconnected"));
}
void InProcessEPC::callJITDispatch(uint64_t CallId, void *HandlerTag,
shared::CWrapperFunctionBuffer ArgBytes) {
assert(C->ReturnJITDispatchResult && "ReturnJITDispatchResult not set");
LLVM_DEBUG(dbgs() << "InProcessEPC: JIT-dispatch call id " << CallId << " to "
<< HandlerTag << "\n");
getExecutionSession().runJITDispatchHandler(
[this, CallId](shared::WrapperFunctionBuffer ResultBytes) {
LLVM_DEBUG(dbgs() << "InProcessEPC: Returning JIT-dispatch result for "
"call id "
<< CallId << "\n");
if (C->EnterMessageScope(C)) {
C->ReturnJITDispatchResult(C->IPCA, CallId, ResultBytes.release());
C->LeaveMessageScope(C);
}
},
ExecutorAddr::fromPtr(HandlerTag),
shared::WrapperFunctionBuffer(ArgBytes));
}
void InProcessEPC::callJITDispatchEntry(
void *IPEPC, uint64_t CallId, void *HandlerTag,
shared::CWrapperFunctionBuffer ArgBytes) {
static_cast<InProcessEPC *>(IPEPC)->callJITDispatch(CallId, HandlerTag,
ArgBytes);
}
void InProcessEPC::returnWrapperResult(
uint64_t CallId, shared::CWrapperFunctionBuffer ResultBytes) {
LLVM_DEBUG(dbgs() << "InProcessEPC: Wrapper result for call id " << CallId
<< "\n");
IncomingWFRHandler H;
{
std::scoped_lock<std::mutex> Lock(M);
auto I = PendingCallWrapperResults.find(CallId);
if (I != PendingCallWrapperResults.end()) {
H = std::move(I->second);
PendingCallWrapperResults.erase(I);
}
}
if (!H) {
getExecutionSession().reportError(make_error<StringError>(
"InProcessEPC received result for invalid call id " + Twine(CallId),
inconvertibleErrorCode()));
return;
}
H(shared::WrapperFunctionBuffer(ResultBytes));
}
void InProcessEPC::returnWrapperResultEntry(
void *IPEPC, uint64_t CallId, shared::CWrapperFunctionBuffer ResultBytes) {
static_cast<InProcessEPC *>(IPEPC)->returnWrapperResult(CallId, ResultBytes);
}
} // namespace llvm::orc