Bring the wrapper-function call and return prototypes into line with the
convention adopted for orc_rt_Session_callController: the session handle
comes first, then the payload buffer, then the completion callback
immediately followed by its threaded token, with the token last.
orc_rt_WrapperFunctionReturn: (S, CallId, ResultBytes)
-> (S, ResultBytes, CallId)
orc_rt_WrapperFunction: (S, CallId, Return, ArgBytes)
-> (S, ArgBytes, Return, CallId)
The C++ adapters that mirror these signatures are updated to match --
WrapperFunction::handle, SPSWrapperFunction::handle, the
ORC_RT_SPS_WRAPPER macro, and StructuredYield -- along with the Session
dispatch path and the affected unit tests. No functional change beyond
argument order.
287 lines
8.8 KiB
C++
287 lines
8.8 KiB
C++
//===- 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(
|
|
OnControllerCallReturn OnComplete, orc_rt_ControllerHandlerTag 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
|
|
// Synchronous failure: the connection is already gone, so the call can't be
|
|
// sent. The caller is still on the stack, so fail the handler inline.
|
|
failControllerCallInline(std::move(OnComplete));
|
|
}
|
|
|
|
void InProcessControllerAccess::sendWrapperResult(
|
|
WrapperFunctionBuffer ResultBytes, uint64_t CallId) {
|
|
assert(C && "sendWrapperResult called before connect");
|
|
if (C->EnterMessageScope(C)) {
|
|
C->ReturnWrapperResult(C->IPEPC, CallId, ResultBytes.release());
|
|
C->LeaveMessageScope(C);
|
|
}
|
|
}
|
|
|
|
uint64_t InProcessControllerAccess::registerPendingHandler(
|
|
OnControllerCallReturn 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)
|
|
failPendingControllerCall(std::move(H));
|
|
|
|
notifyDisconnected();
|
|
}
|
|
|
|
void InProcessControllerAccess::callWrapper(
|
|
uint64_t CallId, void *Fn, orc_rt_WrapperFunctionBuffer ArgBytes) {
|
|
handleWrapperCall(reinterpret_cast<orc_rt_WrapperFunction>(Fn),
|
|
WrapperFunctionBuffer(ArgBytes), CallId);
|
|
}
|
|
|
|
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) {
|
|
|
|
OnControllerCallReturn 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"));
|
|
|
|
handleControllerCallResult(std::move(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
|