416 lines
15 KiB
C
416 lines
15 KiB
C
|
//===--- OrcRPCTargetProcessControl.h - Remote target control ---*- C++ -*-===//
|
||
|
//
|
||
|
// 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
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
//
|
||
|
// Utilities for interacting with target processes.
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#ifndef LLVM_EXECUTIONENGINE_ORC_ORCRPCTARGETPROCESSCONTROL_H
|
||
|
#define LLVM_EXECUTIONENGINE_ORC_ORCRPCTARGETPROCESSCONTROL_H
|
||
|
|
||
|
#include "llvm/ExecutionEngine/Orc/Shared/RPCUtils.h"
|
||
|
#include "llvm/ExecutionEngine/Orc/Shared/RawByteChannel.h"
|
||
|
#include "llvm/ExecutionEngine/Orc/TargetProcess/OrcRPCTPCServer.h"
|
||
|
#include "llvm/ExecutionEngine/Orc/TargetProcessControl.h"
|
||
|
#include "llvm/Support/MSVCErrorWorkarounds.h"
|
||
|
|
||
|
namespace llvm {
|
||
|
namespace orc {
|
||
|
|
||
|
/// JITLinkMemoryManager implementation for a process connected via an ORC RPC
|
||
|
/// endpoint.
|
||
|
template <typename OrcRPCTPCImplT>
|
||
|
class OrcRPCTPCJITLinkMemoryManager : public jitlink::JITLinkMemoryManager {
|
||
|
private:
|
||
|
struct HostAlloc {
|
||
|
std::unique_ptr<char[]> Mem;
|
||
|
uint64_t Size;
|
||
|
};
|
||
|
|
||
|
struct TargetAlloc {
|
||
|
JITTargetAddress Address = 0;
|
||
|
uint64_t AllocatedSize = 0;
|
||
|
};
|
||
|
|
||
|
using HostAllocMap = DenseMap<int, HostAlloc>;
|
||
|
using TargetAllocMap = DenseMap<int, TargetAlloc>;
|
||
|
|
||
|
public:
|
||
|
class OrcRPCAllocation : public Allocation {
|
||
|
public:
|
||
|
OrcRPCAllocation(OrcRPCTPCJITLinkMemoryManager<OrcRPCTPCImplT> &Parent,
|
||
|
HostAllocMap HostAllocs, TargetAllocMap TargetAllocs)
|
||
|
: Parent(Parent), HostAllocs(std::move(HostAllocs)),
|
||
|
TargetAllocs(std::move(TargetAllocs)) {
|
||
|
assert(HostAllocs.size() == TargetAllocs.size() &&
|
||
|
"HostAllocs size should match TargetAllocs");
|
||
|
}
|
||
|
|
||
|
~OrcRPCAllocation() override {
|
||
|
assert(TargetAllocs.empty() && "failed to deallocate");
|
||
|
}
|
||
|
|
||
|
MutableArrayRef<char> getWorkingMemory(ProtectionFlags Seg) override {
|
||
|
auto I = HostAllocs.find(Seg);
|
||
|
assert(I != HostAllocs.end() && "No host allocation for segment");
|
||
|
auto &HA = I->second;
|
||
|
return {HA.Mem.get(), static_cast<size_t>(HA.Size)};
|
||
|
}
|
||
|
|
||
|
JITTargetAddress getTargetMemory(ProtectionFlags Seg) override {
|
||
|
auto I = TargetAllocs.find(Seg);
|
||
|
assert(I != TargetAllocs.end() && "No target allocation for segment");
|
||
|
return I->second.Address;
|
||
|
}
|
||
|
|
||
|
void finalizeAsync(FinalizeContinuation OnFinalize) override {
|
||
|
|
||
|
std::vector<tpctypes::BufferWrite> BufferWrites;
|
||
|
orcrpctpc::ReleaseOrFinalizeMemRequest FMR;
|
||
|
|
||
|
for (auto &KV : HostAllocs) {
|
||
|
assert(TargetAllocs.count(KV.first) &&
|
||
|
"No target allocation for buffer");
|
||
|
auto &HA = KV.second;
|
||
|
auto &TA = TargetAllocs[KV.first];
|
||
|
BufferWrites.push_back({TA.Address, StringRef(HA.Mem.get(), HA.Size)});
|
||
|
FMR.push_back({orcrpctpc::toWireProtectionFlags(
|
||
|
static_cast<sys::Memory::ProtectionFlags>(KV.first)),
|
||
|
TA.Address, TA.AllocatedSize});
|
||
|
}
|
||
|
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
dbgs() << "finalizeAsync " << (void *)this << ":\n";
|
||
|
auto FMRI = FMR.begin();
|
||
|
for (auto &B : BufferWrites) {
|
||
|
auto Prot = FMRI->Prot;
|
||
|
++FMRI;
|
||
|
dbgs() << " Writing " << formatv("{0:x16}", B.Buffer.size())
|
||
|
<< " bytes to " << ((Prot & orcrpctpc::WPF_Read) ? 'R' : '-')
|
||
|
<< ((Prot & orcrpctpc::WPF_Write) ? 'W' : '-')
|
||
|
<< ((Prot & orcrpctpc::WPF_Exec) ? 'X' : '-')
|
||
|
<< " segment: local " << (const void *)B.Buffer.data()
|
||
|
<< " -> target " << formatv("{0:x16}", B.Address) << "\n";
|
||
|
}
|
||
|
});
|
||
|
if (auto Err =
|
||
|
Parent.Parent.getMemoryAccess().writeBuffers(BufferWrites)) {
|
||
|
OnFinalize(std::move(Err));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DEBUG_WITH_TYPE("orc", dbgs() << " Applying permissions...\n");
|
||
|
if (auto Err =
|
||
|
Parent.getEndpoint().template callAsync<orcrpctpc::FinalizeMem>(
|
||
|
[OF = std::move(OnFinalize)](Error Err2) {
|
||
|
// FIXME: Dispatch to work queue.
|
||
|
std::thread([OF = std::move(OF),
|
||
|
Err3 = std::move(Err2)]() mutable {
|
||
|
DEBUG_WITH_TYPE(
|
||
|
"orc", { dbgs() << " finalizeAsync complete\n"; });
|
||
|
OF(std::move(Err3));
|
||
|
}).detach();
|
||
|
return Error::success();
|
||
|
},
|
||
|
FMR)) {
|
||
|
DEBUG_WITH_TYPE("orc", dbgs() << " failed.\n");
|
||
|
Parent.getEndpoint().abandonPendingResponses();
|
||
|
Parent.reportError(std::move(Err));
|
||
|
}
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
dbgs() << "Leaving finalizeAsync (finalization may continue in "
|
||
|
"background)\n";
|
||
|
});
|
||
|
}
|
||
|
|
||
|
Error deallocate() override {
|
||
|
orcrpctpc::ReleaseOrFinalizeMemRequest RMR;
|
||
|
for (auto &KV : TargetAllocs)
|
||
|
RMR.push_back({orcrpctpc::toWireProtectionFlags(
|
||
|
static_cast<sys::Memory::ProtectionFlags>(KV.first)),
|
||
|
KV.second.Address, KV.second.AllocatedSize});
|
||
|
TargetAllocs.clear();
|
||
|
|
||
|
return Parent.getEndpoint().template callB<orcrpctpc::ReleaseMem>(RMR);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
OrcRPCTPCJITLinkMemoryManager<OrcRPCTPCImplT> &Parent;
|
||
|
HostAllocMap HostAllocs;
|
||
|
TargetAllocMap TargetAllocs;
|
||
|
};
|
||
|
|
||
|
OrcRPCTPCJITLinkMemoryManager(OrcRPCTPCImplT &Parent) : Parent(Parent) {}
|
||
|
|
||
|
Expected<std::unique_ptr<Allocation>>
|
||
|
allocate(const jitlink::JITLinkDylib *JD,
|
||
|
const SegmentsRequestMap &Request) override {
|
||
|
orcrpctpc::ReserveMemRequest RMR;
|
||
|
HostAllocMap HostAllocs;
|
||
|
|
||
|
for (auto &KV : Request) {
|
||
|
assert(KV.second.getContentSize() <= std::numeric_limits<size_t>::max() &&
|
||
|
"Content size is out-of-range for host");
|
||
|
|
||
|
RMR.push_back({orcrpctpc::toWireProtectionFlags(
|
||
|
static_cast<sys::Memory::ProtectionFlags>(KV.first)),
|
||
|
KV.second.getContentSize() + KV.second.getZeroFillSize(),
|
||
|
KV.second.getAlignment()});
|
||
|
HostAllocs[KV.first] = {
|
||
|
std::make_unique<char[]>(KV.second.getContentSize()),
|
||
|
KV.second.getContentSize()};
|
||
|
}
|
||
|
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
dbgs() << "Orc remote memmgr got request:\n";
|
||
|
for (auto &KV : Request)
|
||
|
dbgs() << " permissions: "
|
||
|
<< ((KV.first & sys::Memory::MF_READ) ? 'R' : '-')
|
||
|
<< ((KV.first & sys::Memory::MF_WRITE) ? 'W' : '-')
|
||
|
<< ((KV.first & sys::Memory::MF_EXEC) ? 'X' : '-')
|
||
|
<< ", content size: "
|
||
|
<< formatv("{0:x16}", KV.second.getContentSize())
|
||
|
<< " + zero-fill-size: "
|
||
|
<< formatv("{0:x16}", KV.second.getZeroFillSize())
|
||
|
<< ", align: " << KV.second.getAlignment() << "\n";
|
||
|
});
|
||
|
|
||
|
// FIXME: LLVM RPC needs to be fixed to support alt
|
||
|
// serialization/deserialization on return types. For now just
|
||
|
// translate from std::map to DenseMap manually.
|
||
|
auto TmpTargetAllocs =
|
||
|
Parent.getEndpoint().template callB<orcrpctpc::ReserveMem>(RMR);
|
||
|
if (!TmpTargetAllocs)
|
||
|
return TmpTargetAllocs.takeError();
|
||
|
|
||
|
if (TmpTargetAllocs->size() != RMR.size())
|
||
|
return make_error<StringError>(
|
||
|
"Number of target allocations does not match request",
|
||
|
inconvertibleErrorCode());
|
||
|
|
||
|
TargetAllocMap TargetAllocs;
|
||
|
for (auto &E : *TmpTargetAllocs)
|
||
|
TargetAllocs[orcrpctpc::fromWireProtectionFlags(E.Prot)] = {
|
||
|
E.Address, E.AllocatedSize};
|
||
|
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
auto HAI = HostAllocs.begin();
|
||
|
for (auto &KV : TargetAllocs)
|
||
|
dbgs() << " permissions: "
|
||
|
<< ((KV.first & sys::Memory::MF_READ) ? 'R' : '-')
|
||
|
<< ((KV.first & sys::Memory::MF_WRITE) ? 'W' : '-')
|
||
|
<< ((KV.first & sys::Memory::MF_EXEC) ? 'X' : '-')
|
||
|
<< " assigned local " << (void *)HAI->second.Mem.get()
|
||
|
<< ", target " << formatv("{0:x16}", KV.second.Address) << "\n";
|
||
|
});
|
||
|
|
||
|
return std::make_unique<OrcRPCAllocation>(*this, std::move(HostAllocs),
|
||
|
std::move(TargetAllocs));
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
void reportError(Error Err) { Parent.reportError(std::move(Err)); }
|
||
|
|
||
|
decltype(std::declval<OrcRPCTPCImplT>().getEndpoint()) getEndpoint() {
|
||
|
return Parent.getEndpoint();
|
||
|
}
|
||
|
|
||
|
OrcRPCTPCImplT &Parent;
|
||
|
};
|
||
|
|
||
|
/// TargetProcessControl::MemoryAccess implementation for a process connected
|
||
|
/// via an ORC RPC endpoint.
|
||
|
template <typename OrcRPCTPCImplT>
|
||
|
class OrcRPCTPCMemoryAccess : public TargetProcessControl::MemoryAccess {
|
||
|
public:
|
||
|
OrcRPCTPCMemoryAccess(OrcRPCTPCImplT &Parent) : Parent(Parent) {}
|
||
|
|
||
|
void writeUInt8s(ArrayRef<tpctypes::UInt8Write> Ws,
|
||
|
WriteResultFn OnWriteComplete) override {
|
||
|
writeViaRPC<orcrpctpc::WriteUInt8s>(Ws, std::move(OnWriteComplete));
|
||
|
}
|
||
|
|
||
|
void writeUInt16s(ArrayRef<tpctypes::UInt16Write> Ws,
|
||
|
WriteResultFn OnWriteComplete) override {
|
||
|
writeViaRPC<orcrpctpc::WriteUInt16s>(Ws, std::move(OnWriteComplete));
|
||
|
}
|
||
|
|
||
|
void writeUInt32s(ArrayRef<tpctypes::UInt32Write> Ws,
|
||
|
WriteResultFn OnWriteComplete) override {
|
||
|
writeViaRPC<orcrpctpc::WriteUInt32s>(Ws, std::move(OnWriteComplete));
|
||
|
}
|
||
|
|
||
|
void writeUInt64s(ArrayRef<tpctypes::UInt64Write> Ws,
|
||
|
WriteResultFn OnWriteComplete) override {
|
||
|
writeViaRPC<orcrpctpc::WriteUInt64s>(Ws, std::move(OnWriteComplete));
|
||
|
}
|
||
|
|
||
|
void writeBuffers(ArrayRef<tpctypes::BufferWrite> Ws,
|
||
|
WriteResultFn OnWriteComplete) override {
|
||
|
writeViaRPC<orcrpctpc::WriteBuffers>(Ws, std::move(OnWriteComplete));
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
template <typename WriteRPCFunction, typename WriteElementT>
|
||
|
void writeViaRPC(ArrayRef<WriteElementT> Ws, WriteResultFn OnWriteComplete) {
|
||
|
if (auto Err = Parent.getEndpoint().template callAsync<WriteRPCFunction>(
|
||
|
[OWC = std::move(OnWriteComplete)](Error Err2) mutable -> Error {
|
||
|
OWC(std::move(Err2));
|
||
|
return Error::success();
|
||
|
},
|
||
|
Ws)) {
|
||
|
Parent.reportError(std::move(Err));
|
||
|
Parent.getEndpoint().abandonPendingResponses();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
OrcRPCTPCImplT &Parent;
|
||
|
};
|
||
|
|
||
|
// TargetProcessControl for a process connected via an ORC RPC Endpoint.
|
||
|
template <typename RPCEndpointT>
|
||
|
class OrcRPCTargetProcessControlBase : public TargetProcessControl {
|
||
|
public:
|
||
|
using ErrorReporter = unique_function<void(Error)>;
|
||
|
|
||
|
using OnCloseConnectionFunction = unique_function<Error(Error)>;
|
||
|
|
||
|
OrcRPCTargetProcessControlBase(std::shared_ptr<SymbolStringPool> SSP,
|
||
|
RPCEndpointT &EP, ErrorReporter ReportError)
|
||
|
: TargetProcessControl(std::move(SSP)),
|
||
|
ReportError(std::move(ReportError)), EP(EP) {}
|
||
|
|
||
|
void reportError(Error Err) { ReportError(std::move(Err)); }
|
||
|
|
||
|
RPCEndpointT &getEndpoint() { return EP; }
|
||
|
|
||
|
Expected<tpctypes::DylibHandle> loadDylib(const char *DylibPath) override {
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
dbgs() << "Loading dylib \"" << (DylibPath ? DylibPath : "") << "\" ";
|
||
|
if (!DylibPath)
|
||
|
dbgs() << "(process symbols)";
|
||
|
dbgs() << "\n";
|
||
|
});
|
||
|
if (!DylibPath)
|
||
|
DylibPath = "";
|
||
|
auto H = EP.template callB<orcrpctpc::LoadDylib>(DylibPath);
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
if (H)
|
||
|
dbgs() << " got handle " << formatv("{0:x16}", *H) << "\n";
|
||
|
else
|
||
|
dbgs() << " error, unable to load\n";
|
||
|
});
|
||
|
return H;
|
||
|
}
|
||
|
|
||
|
Expected<std::vector<tpctypes::LookupResult>>
|
||
|
lookupSymbols(ArrayRef<LookupRequest> Request) override {
|
||
|
std::vector<orcrpctpc::RemoteLookupRequest> RR;
|
||
|
for (auto &E : Request) {
|
||
|
RR.push_back({});
|
||
|
RR.back().first = E.Handle;
|
||
|
for (auto &KV : E.Symbols)
|
||
|
RR.back().second.push_back(
|
||
|
{(*KV.first).str(),
|
||
|
KV.second == SymbolLookupFlags::WeaklyReferencedSymbol});
|
||
|
}
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
dbgs() << "Compound lookup:\n";
|
||
|
for (auto &R : Request) {
|
||
|
dbgs() << " In " << formatv("{0:x16}", R.Handle) << ": {";
|
||
|
bool First = true;
|
||
|
for (auto &KV : R.Symbols) {
|
||
|
dbgs() << (First ? "" : ",") << " " << *KV.first;
|
||
|
First = false;
|
||
|
}
|
||
|
dbgs() << " }\n";
|
||
|
}
|
||
|
});
|
||
|
return EP.template callB<orcrpctpc::LookupSymbols>(RR);
|
||
|
}
|
||
|
|
||
|
Expected<int32_t> runAsMain(JITTargetAddress MainFnAddr,
|
||
|
ArrayRef<std::string> Args) override {
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
dbgs() << "Running as main: " << formatv("{0:x16}", MainFnAddr)
|
||
|
<< ", args = [";
|
||
|
for (unsigned I = 0; I != Args.size(); ++I)
|
||
|
dbgs() << (I ? "," : "") << " \"" << Args[I] << "\"";
|
||
|
dbgs() << "]\n";
|
||
|
});
|
||
|
auto Result = EP.template callB<orcrpctpc::RunMain>(MainFnAddr, Args);
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
dbgs() << " call to " << formatv("{0:x16}", MainFnAddr);
|
||
|
if (Result)
|
||
|
dbgs() << " returned result " << *Result << "\n";
|
||
|
else
|
||
|
dbgs() << " failed\n";
|
||
|
});
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
Expected<tpctypes::WrapperFunctionResult>
|
||
|
runWrapper(JITTargetAddress WrapperFnAddr,
|
||
|
ArrayRef<uint8_t> ArgBuffer) override {
|
||
|
DEBUG_WITH_TYPE("orc", {
|
||
|
dbgs() << "Running as wrapper function "
|
||
|
<< formatv("{0:x16}", WrapperFnAddr) << " with "
|
||
|
<< formatv("{0:x16}", ArgBuffer.size()) << " argument buffer\n";
|
||
|
});
|
||
|
auto Result =
|
||
|
EP.template callB<orcrpctpc::RunWrapper>(WrapperFnAddr, ArgBuffer);
|
||
|
// dbgs() << "Returned from runWrapper...\n";
|
||
|
return Result;
|
||
|
}
|
||
|
|
||
|
Error closeConnection(OnCloseConnectionFunction OnCloseConnection) {
|
||
|
DEBUG_WITH_TYPE("orc", dbgs() << "Closing connection to remote\n");
|
||
|
return EP.template callAsync<orcrpctpc::CloseConnection>(
|
||
|
std::move(OnCloseConnection));
|
||
|
}
|
||
|
|
||
|
Error closeConnectionAndWait() {
|
||
|
std::promise<MSVCPError> P;
|
||
|
auto F = P.get_future();
|
||
|
if (auto Err = closeConnection([&](Error Err2) -> Error {
|
||
|
P.set_value(std::move(Err2));
|
||
|
return Error::success();
|
||
|
})) {
|
||
|
EP.abandonAllPendingResponses();
|
||
|
return joinErrors(std::move(Err), F.get());
|
||
|
}
|
||
|
return F.get();
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
/// Subclasses must call this during construction to initialize the
|
||
|
/// TargetTriple and PageSize members.
|
||
|
Error initializeORCRPCTPCBase() {
|
||
|
if (auto TripleOrErr = EP.template callB<orcrpctpc::GetTargetTriple>())
|
||
|
TargetTriple = Triple(*TripleOrErr);
|
||
|
else
|
||
|
return TripleOrErr.takeError();
|
||
|
|
||
|
if (auto PageSizeOrErr = EP.template callB<orcrpctpc::GetPageSize>())
|
||
|
PageSize = *PageSizeOrErr;
|
||
|
else
|
||
|
return PageSizeOrErr.takeError();
|
||
|
|
||
|
return Error::success();
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
ErrorReporter ReportError;
|
||
|
RPCEndpointT &EP;
|
||
|
};
|
||
|
|
||
|
} // end namespace orc
|
||
|
} // end namespace llvm
|
||
|
|
||
|
#endif // LLVM_EXECUTIONENGINE_ORC_ORCRPCTARGETPROCESSCONTROL_H
|