479 changes: 479 additions & 0 deletions llvm/include/llvm/ExecutionEngine/Orc/OrcRemoteTargetServer.h

Large diffs are not rendered by default.

207 changes: 207 additions & 0 deletions llvm/include/llvm/ExecutionEngine/Orc/RPCChannel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// -*- c++ -*-

#ifndef LLVM_EXECUTIONENGINE_ORC_RPCCHANNEL_H
#define LLVM_EXECUTIONENGINE_ORC_RPCCHANNEL_H

#include "OrcError.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Endian.h"

#include <system_error>
#include <unistd.h>

namespace llvm {
namespace orc {
namespace remote {

/// Interface for byte-streams to be used with RPC.
class RPCChannel {
public:
virtual ~RPCChannel() {}

/// Read Size bytes from the stream into *Dst.
virtual std::error_code readBytes(char *Dst, unsigned Size) = 0;

/// Read size bytes from *Src and append them to the stream.
virtual std::error_code appendBytes(const char *Src, unsigned Size) = 0;

/// Flush the stream if possible.
virtual std::error_code send() = 0;
};

/// RPC channel that reads from and writes from file descriptors.
class FDRPCChannel : public RPCChannel {
public:
FDRPCChannel(int InFD, int OutFD) : InFD(InFD), OutFD(OutFD) {}

std::error_code readBytes(char *Dst, unsigned Size) override {
assert(Dst && "Attempt to read into null.");
ssize_t ReadResult = ::read(InFD, Dst, Size);
if (ReadResult != Size)
return std::error_code(errno, std::generic_category());
return std::error_code();
}

std::error_code appendBytes(const char *Src, unsigned Size) override {
assert(Src && "Attempt to append from null.");
ssize_t WriteResult = ::write(OutFD, Src, Size);
if (WriteResult != Size)
std::error_code(errno, std::generic_category());
return std::error_code();
}

std::error_code send() override { return std::error_code(); }

private:
int InFD, OutFD;
};

/// RPC channel serialization for a variadic list of arguments.
template <typename T, typename... Ts>
std::error_code serialize_seq(RPCChannel &C, const T &Arg, const Ts &... Args) {
if (auto EC = serialize(C, Arg))
return EC;
return serialize_seq(C, Args...);
}

/// RPC channel serialization for an (empty) variadic list of arguments.
inline std::error_code serialize_seq(RPCChannel &C) {
return std::error_code();
}

/// RPC channel deserialization for a variadic list of arguments.
template <typename T, typename... Ts>
std::error_code deserialize_seq(RPCChannel &C, T &Arg, Ts &... Args) {
if (auto EC = deserialize(C, Arg))
return EC;
return deserialize_seq(C, Args...);
}

/// RPC channel serialization for an (empty) variadic list of arguments.
inline std::error_code deserialize_seq(RPCChannel &C) {
return std::error_code();
}

/// RPC channel serialization for integer primitives.
template <typename T>
typename std::enable_if<
std::is_same<T, uint64_t>::value || std::is_same<T, int64_t>::value ||
std::is_same<T, uint32_t>::value || std::is_same<T, int32_t>::value ||
std::is_same<T, uint16_t>::value || std::is_same<T, int16_t>::value ||
std::is_same<T, uint8_t>::value || std::is_same<T, int8_t>::value,
std::error_code>::type
serialize(RPCChannel &C, T V) {
support::endian::byte_swap<T, support::big>(V);
return C.appendBytes(reinterpret_cast<const char *>(&V), sizeof(T));
}

/// RPC channel deserialization for integer primitives.
template <typename T>
typename std::enable_if<
std::is_same<T, uint64_t>::value || std::is_same<T, int64_t>::value ||
std::is_same<T, uint32_t>::value || std::is_same<T, int32_t>::value ||
std::is_same<T, uint16_t>::value || std::is_same<T, int16_t>::value ||
std::is_same<T, uint8_t>::value || std::is_same<T, int8_t>::value,
std::error_code>::type
deserialize(RPCChannel &C, T &V) {
if (auto EC = C.readBytes(reinterpret_cast<char *>(&V), sizeof(T)))
return EC;
support::endian::byte_swap<T, support::big>(V);
return std::error_code();
}

/// RPC channel serialization for enums.
template <typename T>
typename std::enable_if<std::is_enum<T>::value, std::error_code>::type
serialize(RPCChannel &C, T V) {
return serialize(C, static_cast<typename std::underlying_type<T>::type>(V));
}

/// RPC channel deserialization for enums.
template <typename T>
typename std::enable_if<std::is_enum<T>::value, std::error_code>::type
deserialize(RPCChannel &C, T &V) {
typename std::underlying_type<T>::type Tmp;
std::error_code EC = deserialize(C, Tmp);
V = static_cast<T>(Tmp);
return EC;
}

/// RPC channel serialization for bools.
inline std::error_code serialize(RPCChannel &C, bool V) {
uint8_t VN = V ? 1 : 0;
return C.appendBytes(reinterpret_cast<const char *>(&VN), 1);
}

/// RPC channel deserialization for bools.
inline std::error_code deserialize(RPCChannel &C, bool &V) {
uint8_t VN = 0;
if (auto EC = C.readBytes(reinterpret_cast<char *>(&VN), 1))
return EC;

V = (VN != 0) ? true : false;
return std::error_code();
}

/// RPC channel serialization for StringRefs.
/// Note: There is no corresponding deseralization for this, as StringRef
/// doesn't own its memory and so can't hold the deserialized data.
inline std::error_code serialize(RPCChannel &C, StringRef S) {
if (auto EC = serialize(C, static_cast<uint64_t>(S.size())))
return EC;
return C.appendBytes((const char *)S.bytes_begin(), S.size());
}

/// RPC channel serialization for std::strings.
inline std::error_code serialize(RPCChannel &C, const std::string &S) {
return serialize(C, StringRef(S));
}

/// RPC channel deserialization for std::strings.
inline std::error_code deserialize(RPCChannel &C, std::string &S) {
uint64_t Count;
if (auto EC = deserialize(C, Count))
return EC;
S.resize(Count);
return C.readBytes(&S[0], Count);
}

/// RPC channel serialization for ArrayRef<T>.
template <typename T>
std::error_code serialize(RPCChannel &C, const ArrayRef<T> &A) {
if (auto EC = serialize(C, static_cast<uint64_t>(A.size())))
return EC;

for (const auto &E : A)
if (auto EC = serialize(C, E))
return EC;

return std::error_code();
}

/// RPC channel serialization for std::array<T>.
template <typename T>
std::error_code serialize(RPCChannel &C, const std::vector<T> &V) {
return serialize(C, ArrayRef<T>(V));
}

/// RPC channel deserialization for std::array<T>.
template <typename T>
std::error_code deserialize(RPCChannel &C, std::vector<T> &V) {
uint64_t Count = 0;
if (auto EC = deserialize(C, Count))
return EC;

V.resize(Count);
for (auto &E : V)
if (auto EC = deserialize(C, E))
return EC;

return std::error_code();
}

} // end namespace remote
} // end namespace orc
} // end namespace llvm

#endif
222 changes: 222 additions & 0 deletions llvm/include/llvm/ExecutionEngine/Orc/RPCUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
//===----- RPCUTils.h - Basic tilities for building RPC APIs ----*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Basic utilities for building RPC APIs.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_EXECUTIONENGINE_ORC_RPCUTILS_H
#define LLVM_EXECUTIONENGINE_ORC_RPCUTILS_H

#include "llvm/ADT/STLExtras.h"

namespace llvm {
namespace orc {
namespace remote {

/// Contains primitive utilities for defining, calling and handling calls to
/// remote procedures. ChannelT is a bidirectional stream conforming to the
/// RPCChannel interface (see RPCChannel.h), and ProcedureIdT is a procedure
/// identifier type that must be serializable on ChannelT.
///
/// These utilities support the construction of very primitive RPC utilities.
/// Their intent is to ensure correct serialization and deserialization of
/// procedure arguments, and to keep the client and server's view of the API in
/// sync.
///
/// These utilities do not support return values. These can be handled by
/// declaring a corresponding '.*Response' procedure and expecting it after a
/// call). They also do not support versioning: the client and server *must* be
/// compiled with the same procedure definitions.
///
///
///
/// Overview (see comments individual types/methods for details):
///
/// Procedure<Id, Args...> :
///
/// associates a unique serializable id with an argument list.
///
///
/// call<Proc>(Channel, Args...) :
///
/// Calls the remote procedure 'Proc' by serializing Proc's id followed by its
/// arguments and sending the resulting bytes to 'Channel'.
///
///
/// handle<Proc>(Channel, <functor matching std::error_code(Args...)> :
///
/// Handles a call to 'Proc' by deserializing its arguments and calling the
/// given functor. This assumes that the id for 'Proc' has already been
/// deserialized.
///
/// expect<Proc>(Channel, <functor matching std::error_code(Args...)> :
///
/// The same as 'handle', except that the procedure id should not have been
/// read yet. Expect will deserialize the id and assert that it matches Proc's
/// id. If it does not, and unexpected RPC call error is returned.

template <typename ChannelT, typename ProcedureIdT = uint32_t> class RPC {
public:
/// Utility class for defining/referring to RPC procedures.
///
/// Typedefs of this utility are used when calling/handling remote procedures.
///
/// ProcId should be a unique value of ProcedureIdT (i.e. not used with any
/// other Procedure typedef in the RPC API being defined.
///
/// the template argument Ts... gives the argument list for the remote
/// procedure.
///
/// E.g.
///
/// typedef Procedure<0, bool> Proc1;
/// typedef Procedure<1, std::string, std::vector<int>> Proc2;
///
/// if (auto EC = call<Proc1>(Channel, true))
/// /* handle EC */;
///
/// if (auto EC = expect<Proc2>(Channel,
/// [](std::string &S, std::vector<int> &V) {
/// // Stuff.
/// return std::error_code();
/// })
/// /* handle EC */;
///
template <ProcedureIdT ProcId, typename... Ts> class Procedure {
public:
static const ProcedureIdT Id = ProcId;
};

private:
template <typename Proc> class CallHelper {};

template <ProcedureIdT ProcId, typename... ArgTs>
class CallHelper<Procedure<ProcId, ArgTs...>> {
public:
static std::error_code call(ChannelT &C, const ArgTs &... Args) {
if (auto EC = serialize(C, ProcId))
return EC;
// If you see a compile-error on this line you're probably calling a
// function with the wrong signature.
return serialize_seq(C, Args...);
}
};

template <typename Proc> class HandlerHelper {};

template <ProcedureIdT ProcId, typename... ArgTs>
class HandlerHelper<Procedure<ProcId, ArgTs...>> {
public:
template <typename HandlerT>
static std::error_code handle(ChannelT &C, HandlerT Handler) {
return readAndHandle(C, Handler, llvm::index_sequence_for<ArgTs...>());
}

private:
template <typename HandlerT, size_t... Is>
static std::error_code readAndHandle(ChannelT &C, HandlerT Handler,
llvm::index_sequence<Is...> _) {
std::tuple<ArgTs...> RPCArgs;
if (auto EC = deserialize_seq(C, std::get<Is>(RPCArgs)...))
return EC;
return Handler(std::get<Is>(RPCArgs)...);
}
};

template <typename... ArgTs> class ReadArgs {
public:
std::error_code operator()() { return std::error_code(); }
};

template <typename ArgT, typename... ArgTs>
class ReadArgs<ArgT, ArgTs...> : public ReadArgs<ArgTs...> {
public:
ReadArgs(ArgT &Arg, ArgTs &... Args)
: ReadArgs<ArgTs...>(Args...), Arg(Arg) {}

std::error_code operator()(ArgT &ArgVal, ArgTs &... ArgVals) {
this->Arg = std::move(ArgVal);
return ReadArgs<ArgTs...>::operator()(ArgVals...);
}

private:
ArgT &Arg;
};

public:
/// Serialize Args... to channel C, but do not call C.send().
///
/// For buffered channels, this can be used to queue up several calls before
/// flushing the channel.
template <typename Proc, typename... ArgTs>
static std::error_code appendCall(ChannelT &C, const ArgTs &... Args) {
return CallHelper<Proc>::call(C, Args...);
}

/// Serialize Args... to channel C and call C.send().
template <typename Proc, typename... ArgTs>
static std::error_code call(ChannelT &C, const ArgTs &... Args) {
if (auto EC = appendCall<Proc>(C, Args...))
return EC;
return C.send();
}

/// Deserialize and return an enum whose underlying type is ProcedureIdT.
static std::error_code getNextProcId(ChannelT &C, ProcedureIdT &Id) {
return deserialize(C, Id);
}

/// Deserialize args for Proc from C and call Handler. The signature of
/// handler must conform to 'std::error_code(Args...)' where Args... matches
/// the arguments used in the Proc typedef.
template <typename Proc, typename HandlerT>
static std::error_code handle(ChannelT &C, HandlerT Handler) {
return HandlerHelper<Proc>::handle(C, Handler);
}

/// Deserialize a ProcedureIdT from C and verify it matches the id for Proc.
/// If the id does match, deserialize the arguments and call the handler
/// (similarly to handle).
/// If the id does not match, return an unexpect RPC call error and do not
/// deserialize any further bytes.
template <typename Proc, typename HandlerT>
static std::error_code expect(ChannelT &C, HandlerT Handler) {
ProcedureIdT ProcId;
if (auto EC = getNextProcId(C, ProcId))
return EC;
if (ProcId != Proc::Id)
return orcError(OrcErrorCode::UnexpectedRPCCall);
return handle<Proc>(C, Handler);
}

/// Helper for handling setter procedures - this method returns a functor that
/// sets the variables referred to by Args... to values deserialized from the
/// channel.
/// E.g.
///
/// typedef Procedure<0, bool, int> Proc1;
///
/// ...
/// bool B;
/// int I;
/// if (auto EC = expect<Proc1>(Channel, readArgs(B, I)))
/// /* Handle Args */ ;
///
template <typename... ArgTs>
static ReadArgs<ArgTs...> readArgs(ArgTs &... Args) {
return ReadArgs<ArgTs...>(Args...);
}
};

} // end namespace remote
} // end namespace orc
} // end namespace llvm

#endif
1 change: 1 addition & 0 deletions llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_llvm_library(LLVMOrcJIT
OrcCBindingsStack.cpp
OrcError.cpp
OrcMCJITReplacement.cpp
OrcRemoteTargetRPCAPI.cpp

ADDITIONAL_HEADER_DIRS
${LLVM_MAIN_INCLUDE_DIR}/llvm/ExecutionEngine/Orc
Expand Down
83 changes: 83 additions & 0 deletions llvm/lib/ExecutionEngine/Orc/OrcRemoteTargetRPCAPI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//===------- OrcRemoteTargetRPCAPI.cpp - ORC Remote API utilities ---------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "llvm/ExecutionEngine/Orc/OrcRemoteTargetRPCAPI.h"

namespace llvm {
namespace orc {
namespace remote {

const char *OrcRemoteTargetRPCAPI::getJITProcIdName(JITProcId Id) {
switch (Id) {
case InvalidId:
return "*** Invalid JITProcId ***";
case CallIntVoidId:
return "CallIntVoid";
case CallIntVoidResponseId:
return "CallIntVoidResponse";
case CallMainId:
return "CallMain";
case CallMainResponseId:
return "CallMainResponse";
case CallVoidVoidId:
return "CallVoidVoid";
case CallVoidVoidResponseId:
return "CallVoidVoidResponse";
case CreateRemoteAllocatorId:
return "CreateRemoteAllocator";
case CreateIndirectStubsOwnerId:
return "CreateIndirectStubsOwner";
case DestroyRemoteAllocatorId:
return "DestroyRemoteAllocator";
case DestroyIndirectStubsOwnerId:
return "DestroyIndirectStubsOwner";
case EmitIndirectStubsId:
return "EmitIndirectStubs";
case EmitIndirectStubsResponseId:
return "EmitIndirectStubsResponse";
case EmitResolverBlockId:
return "EmitResolverBlock";
case EmitTrampolineBlockId:
return "EmitTrampolineBlock";
case EmitTrampolineBlockResponseId:
return "EmitTrampolineBlockResponse";
case GetSymbolAddressId:
return "GetSymbolAddress";
case GetSymbolAddressResponseId:
return "GetSymbolAddressResponse";
case GetRemoteInfoId:
return "GetRemoteInfo";
case GetRemoteInfoResponseId:
return "GetRemoteInfoResponse";
case ReadMemId:
return "ReadMem";
case ReadMemResponseId:
return "ReadMemResponse";
case ReserveMemId:
return "ReserveMem";
case ReserveMemResponseId:
return "ReserveMemResponse";
case RequestCompileId:
return "RequestCompile";
case RequestCompileResponseId:
return "RequestCompileResponse";
case SetProtectionsId:
return "SetProtections";
case TerminateSessionId:
return "TerminateSession";
case WriteMemId:
return "WriteMem";
case WritePtrId:
return "WritePtr";
};
return nullptr;
}
}
}
}
1 change: 1 addition & 0 deletions llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ add_llvm_unittest(OrcJITTests
ObjectTransformLayerTest.cpp
OrcCAPITest.cpp
OrcTestCommon.cpp
RPCUtilsTest.cpp
)
147 changes: 147 additions & 0 deletions llvm/unittests/ExecutionEngine/Orc/RPCUtilsTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//===----------- RPCUtilsTest.cpp - Unit tests the Orc RPC utils ----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "llvm/ExecutionEngine/Orc/RPCChannel.h"
#include "llvm/ExecutionEngine/Orc/RPCUtils.h"
#include "gtest/gtest.h"

#include <queue>

using namespace llvm;
using namespace llvm::orc;
using namespace llvm::orc::remote;

class QueueChannel : public RPCChannel {
public:
QueueChannel(std::queue<char> &Queue) : Queue(Queue) {}

std::error_code readBytes(char *Dst, unsigned Size) override {
while (Size--) {
*Dst++ = Queue.front();
Queue.pop();
}
return std::error_code();
}

std::error_code appendBytes(const char *Src, unsigned Size) override {
while (Size--)
Queue.push(*Src++);
return std::error_code();
}

std::error_code send() override { return std::error_code(); }

private:
std::queue<char> &Queue;
};

class DummyRPC : public testing::Test,
public RPC<QueueChannel> {
public:
typedef Procedure<1, bool> Proc1;
typedef Procedure<2, int8_t,
uint8_t,
int16_t,
uint16_t,
int32_t,
uint32_t,
int64_t,
uint64_t,
bool,
std::string,
std::vector<int>> AllTheTypes;
};


TEST_F(DummyRPC, TestBasic) {
std::queue<char> Queue;
QueueChannel C(Queue);

{
// Make a call to Proc1.
auto EC = call<Proc1>(C, true);
EXPECT_FALSE(EC) << "Simple call over queue failed";
}

{
// Expect a call to Proc1.
auto EC = expect<Proc1>(C,
[&](bool &B) {
EXPECT_EQ(B, true)
<< "Bool serialization broken";
return std::error_code();
});
EXPECT_FALSE(EC) << "Simple expect over queue failed";
}
}

TEST_F(DummyRPC, TestSerialization) {
std::queue<char> Queue;
QueueChannel C(Queue);

{
// Make a call to Proc1.
std::vector<int> v({42, 7});
auto EC = call<AllTheTypes>(C,
-101,
250,
-10000,
10000,
-1000000000,
1000000000,
-10000000000,
10000000000,
true,
"foo",
v);
EXPECT_FALSE(EC) << "Big (serialization test) call over queue failed";
}

{
// Expect a call to Proc1.
auto EC = expect<AllTheTypes>(C,
[&](int8_t &s8,
uint8_t &u8,
int16_t &s16,
uint16_t &u16,
int32_t &s32,
uint32_t &u32,
int64_t &s64,
uint64_t &u64,
bool &b,
std::string &s,
std::vector<int> &v) {

EXPECT_EQ(s8, -101)
<< "int8_t serialization broken";
EXPECT_EQ(u8, 250)
<< "uint8_t serialization broken";
EXPECT_EQ(s16, -10000)
<< "int16_t serialization broken";
EXPECT_EQ(u16, 10000)
<< "uint16_t serialization broken";
EXPECT_EQ(s32, -1000000000)
<< "int32_t serialization broken";
EXPECT_EQ(u32, 1000000000ULL)
<< "uint32_t serialization broken";
EXPECT_EQ(s64, -10000000000)
<< "int64_t serialization broken";
EXPECT_EQ(u64, 10000000000ULL)
<< "uint64_t serialization broken";
EXPECT_EQ(b, true)
<< "bool serialization broken";
EXPECT_EQ(s, "foo")
<< "std::string serialization broken";
EXPECT_EQ(v, std::vector<int>({42, 7}))
<< "std::vector serialization broken";
return std::error_code();
});
EXPECT_FALSE(EC) << "Big (serialization test) call over queue failed";
}
}