blob: adb1644605d35987332a80243e6d901772337fab [file]
//===- InProcessControllerAccess.cpp --------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Contains the implementation of APIs in the
// orc-rt/InProcessControllerAccess.h header.
//
//===----------------------------------------------------------------------===//
#include "orc-rt/InProcessControllerAccess.h"
#include <cassert>
namespace orc_rt {
struct InProcessControllerAccess::ConnectionImpl : public Connection {
public:
ConnectionImpl(InProcessControllerAccess &Instance) {
Retain = &retainEntry;
Release = &releaseEntry;
Disconnect = &disconnectEntry;
EnterMessageScope = &enterMessageScopeEntry;
LeaveMessageScope = &leaveMessageScopeEntry;
IPCA = &Instance;
CallWrapper = InProcessControllerAccess::callWrapperEntry;
ReturnJITDispatchResult =
InProcessControllerAccess::returnJITDispatchResultEntry;
}
private:
void retain() {
std::scoped_lock<std::mutex> Lock(M);
++RefCount;
}
static void retainEntry(Connection *C) {
static_cast<ConnectionImpl *>(C)->retain();
}
int release() {
std::scoped_lock<std::mutex> Lock(M);
--RefCount;
return RefCount == 0;
}
static void releaseEntry(Connection *C) {
if (static_cast<ConnectionImpl *>(C)->release())
delete static_cast<ConnectionImpl *>(C);
}
void disconnect() {
{
std::unique_lock<std::mutex> Lock(M);
if (!Connected)
return;
Connected = false;
CV.wait(Lock, [this]() { return InFlightCalls == 0; });
}
static_cast<InProcessControllerAccess *>(IPCA)->doDisconnect();
}
static void disconnectEntry(Connection *C) {
static_cast<ConnectionImpl *>(C)->disconnect();
}
int enterMessageScope() {
std::scoped_lock<std::mutex> Lock(M);
if (!Connected)
return 0;
++InFlightCalls;
return 1;
}
static int enterMessageScopeEntry(Connection *C) {
return static_cast<ConnectionImpl *>(C)->enterMessageScope();
}
void leaveMessageScope() {
bool NotifyCV = false;
{
std::scoped_lock<std::mutex> Lock(M);
--InFlightCalls;
if (InFlightCalls == 0 && !Connected)
NotifyCV = true;
}
if (NotifyCV)
CV.notify_one();
}
static void leaveMessageScopeEntry(Connection *C) {
static_cast<ConnectionImpl *>(C)->leaveMessageScope();
}
std::mutex M;
std::condition_variable CV;
bool Connected = true;
size_t InFlightCalls = 0;
size_t RefCount = 1;
};
struct InProcessControllerAccess::BootstrapInfoAccessImpl
: public BootstrapInfoAccess {
public:
BootstrapInfoAccessImpl(BootstrapInfo &BI)
: BI(BI), BVI(BI.values().begin()), BSI(BI.symbols().begin()) {
GetPageSize = getPageSizeEntry;
GetTargetTriple = getTargetTripleEntry;
GetNextValue = getNextValueEntry;
GetNextSymbol = getNextSymbolEntry;
}
private:
uint64_t getPageSize() const noexcept { return BI.processInfo().pageSize(); }
static uint64_t getPageSizeEntry(void *BIA) noexcept {
return static_cast<BootstrapInfoAccessImpl *>(BIA)->getPageSize();
}
const char *getTargetTriple() const noexcept {
return BI.processInfo().targetTriple().c_str();
}
static const char *getTargetTripleEntry(void *BIA) {
return static_cast<BootstrapInfoAccessImpl *>(BIA)->getTargetTriple();
}
int getNextValue(const char **Name, const char **Value, uint64_t *ValueSize) {
if (BVI == BI.values().end())
return 0;
*Name = BVI->first.c_str();
*Value = BVI->second.data();
*ValueSize = BVI->second.size();
++BVI;
return 1;
}
static int getNextValueEntry(void *BIA, const char **Name, const char **Value,
uint64_t *ValueSize) {
return static_cast<BootstrapInfoAccessImpl *>(BIA)->getNextValue(
Name, Value, ValueSize);
}
int getNextSymbol(const char **Name, uint64_t *Addr) {
if (BSI == BI.symbols().end())
return 0;
*Name = BSI->first.c_str();
*Addr = ExecutorAddr::fromPtr(BSI->second).getValue();
++BSI;
return 1;
}
static int getNextSymbolEntry(void *BIA, const char **Name, uint64_t *Addr) {
return static_cast<BootstrapInfoAccessImpl *>(BIA)->getNextSymbol(Name,
Addr);
}
BootstrapInfo &BI;
BootstrapInfo::ValueMap::iterator BVI;
SimpleSymbolTable::iterator BSI;
};
InProcessControllerAccess::~InProcessControllerAccess() {
// 'if (C)' to handle the case where the instance is destroyed without
// connect ever being run. (TODO: should calling 'connect' be required?)
if (C)
C->Release(C);
}
void InProcessControllerAccess::connect(BootstrapInfo BI) {
assert(!C && "connect called twice?");
C = new ConnectionImpl(*this);
BootstrapInfoAccessImpl BIA(BI);
if (auto Err = OnConnect(*this, BI, C, &BIA)) {
reportError(std::move(Err));
// Call disconnect. There's a benign race here: if IPEPC also called
// C->Disconnect(C) then it might have acquired responsibility for calling
// InProcessControllerAccess::onDisconnect. In this case control may return
// from disconnect below early, potentially causing us to return from
// 'connect' before notifyDisconnect is called. This may lead to confusing
// logs (since reportError will log error but connect will appear to
// succeed), however onDisconnect will still be called eventually, and the
// Session will detach as if the remote had initiated the action after a
// successful connect.
disconnect();
return;
}
assert(C->IPEPC && "IPEPC not set by OnConnect");
assert(C->CallJITDispatch && "CallJITDispatch not set by OnConnect");
assert(C->ReturnWrapperResult && "ReturnWrapperResult not set by OnConnect");
}
void InProcessControllerAccess::disconnect() {
assert(C && "disconnect called before connect");
C->Disconnect(C);
}
void InProcessControllerAccess::callController(
OnCallHandlerCompleteFn OnComplete, HandlerTag T,
WrapperFunctionBuffer ArgBytes) {
assert(C && "callController called before connect");
if (C->EnterMessageScope(C)) {
C->CallJITDispatch(C->IPEPC, registerPendingHandler(std::move(OnComplete)),
T, ArgBytes.release());
C->LeaveMessageScope(C);
} else
OnComplete(
WrapperFunctionBuffer::createOutOfBandError("connection closed"));
}
void InProcessControllerAccess::sendWrapperResult(
uint64_t CallId, WrapperFunctionBuffer ResultBytes) {
assert(C && "sendWrapperResult called before connect");
if (C->EnterMessageScope(C)) {
C->ReturnWrapperResult(C->IPEPC, CallId, ResultBytes.release());
C->LeaveMessageScope(C);
}
}
uint64_t InProcessControllerAccess::registerPendingHandler(
OnCallHandlerCompleteFn OnComplete) {
std::scoped_lock<std::mutex> Lock(M);
PendingCalls[NextPendingCall] = std::move(OnComplete);
return NextPendingCall++;
}
void InProcessControllerAccess::doDisconnect() {
// Drain pending calls.
PendingCallsMap ToDrain;
{
std::scoped_lock<std::mutex> Lock(M);
ToDrain = std::move(PendingCalls);
}
for (auto &[_, H] : ToDrain)
H(WrapperFunctionBuffer::createOutOfBandError("disconnected"));
notifyDisconnected();
}
void InProcessControllerAccess::callWrapper(
uint64_t CallId, void *Fn, orc_rt_WrapperFunctionBuffer ArgBytes) {
handleWrapperCall(CallId, reinterpret_cast<orc_rt_WrapperFunction>(Fn),
WrapperFunctionBuffer(ArgBytes));
}
void InProcessControllerAccess::callWrapperEntry(
void *IPCA, uint64_t CallId, void *Fn,
orc_rt_WrapperFunctionBuffer ArgBytes) {
assert(IPCA);
static_cast<InProcessControllerAccess *>(IPCA)->callWrapper(CallId, Fn,
ArgBytes);
}
void InProcessControllerAccess::returnJITDispatchResult(
uint64_t CallId, orc_rt_WrapperFunctionBuffer ResultBytes) {
OnCallHandlerCompleteFn OnComplete;
{
std::scoped_lock<std::mutex> Lock(M);
auto I = PendingCalls.find(CallId);
if (I != PendingCalls.end()) {
OnComplete = std::move(I->second);
PendingCalls.erase(I);
}
}
if (!OnComplete)
return reportError(make_error<StringError>("Invalid call id"));
OnComplete(WrapperFunctionBuffer(ResultBytes));
}
void InProcessControllerAccess::returnJITDispatchResultEntry(
void *IPCA, uint64_t CallId, orc_rt_WrapperFunctionBuffer ResultBytes) {
assert(IPCA);
static_cast<InProcessControllerAccess *>(IPCA)->returnJITDispatchResult(
CallId, ResultBytes);
}
} // namespace orc_rt