285 changes: 55 additions & 230 deletions llvm/tools/lli/ChildTarget/ChildTarget.cpp
Original file line number Diff line number Diff line change
@@ -1,244 +1,69 @@
#include "llvm/Config/config.h"
#include "../RPCChannel.h"
#include "../RemoteTarget.h"
#include "../RemoteTargetMessage.h"
#include "llvm/Support/Memory.h"
#include <assert.h>
#include <map>
#include <stdint.h>
#include <string>
#include <vector>
#include "llvm/ExecutionEngine/Orc/OrcArchitectureSupport.h"
#include "llvm/ExecutionEngine/Orc/OrcRemoteTargetServer.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/DynamicLibrary.h"
#include "llvm/Support/Process.h"
#include <sstream>

using namespace llvm;
#include "../RemoteJITUtils.h"

class LLIChildTarget {
public:
void initialize();
LLIMessageType waitForIncomingMessage();
void handleMessage(LLIMessageType messageType);
RemoteTarget *RT;
RPCChannel RPC;
using namespace llvm;
using namespace llvm::orc;
using namespace llvm::sys;

private:
// Incoming message handlers
void handleAllocateSpace();
void handleLoadSection(bool IsCode);
void handleExecute();
#ifdef __x86_64__
typedef OrcX86_64 HostOrcArch;
#else
typedef OrcGenericArchitecture HostOrcArch;
#endif

// Outgoing message handlers
void sendChildActive();
void sendAllocationResult(uint64_t Addr);
void sendLoadStatus(uint32_t Status);
void sendExecutionComplete(int Result);
int main(int argc, char *argv[]) {

// OS-specific functions
void initializeConnection();
int WriteBytes(const void *Data, size_t Size) {
return RPC.WriteBytes(Data, Size) ? Size : -1;
if (argc != 3) {
errs() << "Usage: " << argv[0] << " <input fd> <output fd>\n";
return 1;
}
int ReadBytes(void *Data, size_t Size) {
return RPC.ReadBytes(Data, Size) ? Size : -1;
}

// Communication handles (OS-specific)
void *ConnectionData;
};

int main() {
LLIChildTarget ThisChild;
ThisChild.RT = new RemoteTarget();
ThisChild.initialize();
LLIMessageType MsgType;
do {
MsgType = ThisChild.waitForIncomingMessage();
ThisChild.handleMessage(MsgType);
} while (MsgType != LLI_Terminate &&
MsgType != LLI_Error);
delete ThisChild.RT;
return 0;
}

// Public methods
void LLIChildTarget::initialize() {
RPC.createClient();
sendChildActive();
}
int InFD;
int OutFD;
{
std::istringstream InFDStream(argv[1]), OutFDStream(argv[2]);
InFDStream >> InFD;
OutFDStream >> OutFD;
}

LLIMessageType LLIChildTarget::waitForIncomingMessage() {
int32_t MsgType = -1;
if (ReadBytes(&MsgType, 4) > 0)
return (LLIMessageType)MsgType;
return LLI_Error;
}
if (sys::DynamicLibrary::LoadLibraryPermanently(nullptr)) {
errs() << "Error loading program symbols.\n";
return 1;
}

void LLIChildTarget::handleMessage(LLIMessageType messageType) {
switch (messageType) {
case LLI_AllocateSpace:
handleAllocateSpace();
break;
case LLI_LoadCodeSection:
handleLoadSection(true);
break;
case LLI_LoadDataSection:
handleLoadSection(false);
break;
case LLI_Execute:
handleExecute();
break;
case LLI_Terminate:
RT->stop();
break;
auto SymbolLookup = [](const std::string &Name) {
return RTDyldMemoryManager::getSymbolAddressInProcess(Name);
};

FDRPCChannel Channel(InFD, OutFD);
typedef remote::OrcRemoteTargetServer<FDRPCChannel, HostOrcArch> JITServer;
JITServer Server(Channel, SymbolLookup);

while (1) {
JITServer::JITProcId Id = JITServer::InvalidId;
if (auto EC = Server.getNextProcId(Id)) {
errs() << "Error: " << EC.message() << "\n";
return 1;
}
switch (Id) {
case JITServer::TerminateSessionId:
return 0;
default:
// FIXME: Handle error!
break;
if (auto EC = Server.handleKnownProcedure(Id)) {
errs() << "Error: " << EC.message() << "\n";
return 1;
}
}
}
}

// Incoming message handlers
void LLIChildTarget::handleAllocateSpace() {
// Read and verify the message data size.
uint32_t DataSize = 0;
int rc = ReadBytes(&DataSize, 4);
(void)rc;
assert(rc == 4);
assert(DataSize == 8);

// Read the message arguments.
uint32_t Alignment = 0;
uint32_t AllocSize = 0;
rc = ReadBytes(&Alignment, 4);
assert(rc == 4);
rc = ReadBytes(&AllocSize, 4);
assert(rc == 4);

// Allocate the memory.
uint64_t Addr;
RT->allocateSpace(AllocSize, Alignment, Addr);

// Send AllocationResult message.
sendAllocationResult(Addr);
}

void LLIChildTarget::handleLoadSection(bool IsCode) {
// Read the message data size.
uint32_t DataSize = 0;
int rc = ReadBytes(&DataSize, 4);
(void)rc;
assert(rc == 4);

// Read the target load address.
uint64_t Addr = 0;
rc = ReadBytes(&Addr, 8);
assert(rc == 8);
size_t BufferSize = DataSize - 8;

if (!RT->isAllocatedMemory(Addr, BufferSize))
return sendLoadStatus(LLI_Status_NotAllocated);

// Read section data into previously allocated buffer
rc = ReadBytes((void*)Addr, BufferSize);
if (rc != (int)(BufferSize))
return sendLoadStatus(LLI_Status_IncompleteMsg);

// If IsCode, mark memory executable
if (IsCode)
sys::Memory::InvalidateInstructionCache((void *)Addr, BufferSize);

// Send MarkLoadComplete message.
sendLoadStatus(LLI_Status_Success);
}

void LLIChildTarget::handleExecute() {
// Read the message data size.
uint32_t DataSize = 0;
int rc = ReadBytes(&DataSize, 4);
(void)rc;
assert(rc == 4);
assert(DataSize == 8);

// Read the target address.
uint64_t Addr = 0;
rc = ReadBytes(&Addr, 8);
assert(rc == 8);

// Call function
int32_t Result = -1;
RT->executeCode(Addr, Result);

// Send ExecutionResult message.
sendExecutionComplete(Result);
}

// Outgoing message handlers
void LLIChildTarget::sendChildActive() {
// Write the message type.
uint32_t MsgType = (uint32_t)LLI_ChildActive;
int rc = WriteBytes(&MsgType, 4);
(void)rc;
assert(rc == 4);

// Write the data size.
uint32_t DataSize = 0;
rc = WriteBytes(&DataSize, 4);
assert(rc == 4);
}

void LLIChildTarget::sendAllocationResult(uint64_t Addr) {
// Write the message type.
uint32_t MsgType = (uint32_t)LLI_AllocationResult;
int rc = WriteBytes(&MsgType, 4);
(void)rc;
assert(rc == 4);

// Write the data size.
uint32_t DataSize = 8;
rc = WriteBytes(&DataSize, 4);
assert(rc == 4);

// Write the allocated address.
rc = WriteBytes(&Addr, 8);
assert(rc == 8);
}

void LLIChildTarget::sendLoadStatus(uint32_t Status) {
// Write the message type.
uint32_t MsgType = (uint32_t)LLI_LoadResult;
int rc = WriteBytes(&MsgType, 4);
(void)rc;
assert(rc == 4);

// Write the data size.
uint32_t DataSize = 4;
rc = WriteBytes(&DataSize, 4);
assert(rc == 4);

// Write the result.
rc = WriteBytes(&Status, 4);
assert(rc == 4);
}

void LLIChildTarget::sendExecutionComplete(int Result) {
// Write the message type.
uint32_t MsgType = (uint32_t)LLI_ExecutionResult;
int rc = WriteBytes(&MsgType, 4);
(void)rc;
assert(rc == 4);

close(InFD);
close(OutFD);

// Write the data size.
uint32_t DataSize = 4;
rc = WriteBytes(&DataSize, 4);
assert(rc == 4);

// Write the result.
rc = WriteBytes(&Result, 4);
assert(rc == 4);
return 0;
}

#ifdef LLVM_ON_UNIX
#include "../Unix/RPCChannel.inc"
#endif

#ifdef LLVM_ON_WIN32
#include "../Windows/RPCChannel.inc"
#endif
49 changes: 0 additions & 49 deletions llvm/tools/lli/RPCChannel.h

This file was deleted.

131 changes: 131 additions & 0 deletions llvm/tools/lli/RemoteJITUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//===-- RemoteJITUtils.h - Utilities for remote-JITing with LLI -*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Utilities for remote-JITing with LLI.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_TOOLS_LLI_REMOTEJITUTILS_H
#define LLVM_TOOLS_LLI_REMOTEJITUTILS_H

#include "llvm/ExecutionEngine/Orc/RPCChannel.h"
#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"

#if !defined(_MSC_VER) && !defined(__MINGW32__)
#include <unistd.h>
#else
#include <io.h>
#endif

/// RPC channel that reads from and writes from file descriptors.
class FDRPCChannel : public llvm::orc::remote::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;
};

// launch the remote process (see lli.cpp) and return a channel to it.
std::unique_ptr<FDRPCChannel> launchRemote();

namespace llvm {

// ForwardingMM - Adapter to connect MCJIT to Orc's Remote memory manager.
class ForwardingMemoryManager : public llvm::RTDyldMemoryManager {
public:
void setMemMgr(std::unique_ptr<RuntimeDyld::MemoryManager> MemMgr) {
this->MemMgr = std::move(MemMgr);
}

void setResolver(std::unique_ptr<RuntimeDyld::SymbolResolver> Resolver) {
this->Resolver = std::move(Resolver);
}

uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
unsigned SectionID,
StringRef SectionName) override {
return MemMgr->allocateCodeSection(Size, Alignment, SectionID, SectionName);
}

uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
unsigned SectionID, StringRef SectionName,
bool IsReadOnly) override {
return MemMgr->allocateDataSection(Size, Alignment, SectionID, SectionName,
IsReadOnly);
}

void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign,
uintptr_t RODataSize, uint32_t RODataAlign,
uintptr_t RWDataSize,
uint32_t RWDataAlign) override {
MemMgr->reserveAllocationSpace(CodeSize, CodeAlign, RODataSize, RODataAlign,
RWDataSize, RWDataAlign);
}

bool needsToReserveAllocationSpace() override {
return MemMgr->needsToReserveAllocationSpace();
}

void registerEHFrames(uint8_t *Addr, uint64_t LoadAddr,
size_t Size) override {
MemMgr->registerEHFrames(Addr, LoadAddr, Size);
}

void deregisterEHFrames(uint8_t *Addr, uint64_t LoadAddr,
size_t Size) override {
MemMgr->deregisterEHFrames(Addr, LoadAddr, Size);
}

bool finalizeMemory(std::string *ErrMsg = nullptr) override {
return MemMgr->finalizeMemory(ErrMsg);
}

void notifyObjectLoaded(RuntimeDyld &RTDyld,
const object::ObjectFile &Obj) override {
MemMgr->notifyObjectLoaded(RTDyld, Obj);
}

// Don't hide the sibling notifyObjectLoaded from RTDyldMemoryManager.
using RTDyldMemoryManager::notifyObjectLoaded;

RuntimeDyld::SymbolInfo findSymbol(const std::string &Name) override {
return Resolver->findSymbol(Name);
}

RuntimeDyld::SymbolInfo
findSymbolInLogicalDylib(const std::string &Name) override {
return Resolver->findSymbolInLogicalDylib(Name);
}

private:
std::unique_ptr<RuntimeDyld::MemoryManager> MemMgr;
std::unique_ptr<RuntimeDyld::SymbolResolver> Resolver;
};
}

#endif
174 changes: 0 additions & 174 deletions llvm/tools/lli/RemoteMemoryManager.cpp

This file was deleted.

101 changes: 0 additions & 101 deletions llvm/tools/lli/RemoteMemoryManager.h

This file was deleted.

71 changes: 0 additions & 71 deletions llvm/tools/lli/RemoteTarget.cpp

This file was deleted.

122 changes: 0 additions & 122 deletions llvm/tools/lli/RemoteTarget.h

This file was deleted.

327 changes: 0 additions & 327 deletions llvm/tools/lli/RemoteTargetExternal.cpp

This file was deleted.

143 changes: 0 additions & 143 deletions llvm/tools/lli/RemoteTargetExternal.h

This file was deleted.

85 changes: 0 additions & 85 deletions llvm/tools/lli/RemoteTargetMessage.h

This file was deleted.

122 changes: 0 additions & 122 deletions llvm/tools/lli/Unix/RPCChannel.inc

This file was deleted.

29 changes: 0 additions & 29 deletions llvm/tools/lli/Windows/RPCChannel.inc

This file was deleted.

181 changes: 129 additions & 52 deletions llvm/tools/lli/lli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@
//
//===----------------------------------------------------------------------===//

#include "llvm/IR/LLVMContext.h"
#include "OrcLazyJIT.h"
#include "RemoteMemoryManager.h"
#include "RemoteTarget.h"
#include "RemoteTargetExternal.h"
#include "RemoteJITUtils.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Bitcode/ReaderWriter.h"
#include "llvm/CodeGen/LinkAllCodegenComponents.h"
Expand All @@ -28,6 +26,7 @@
#include "llvm/ExecutionEngine/ObjectCache.h"
#include "llvm/ExecutionEngine/OrcMCJITReplacement.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/ExecutionEngine/Orc/OrcRemoteTargetClient.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
Expand Down Expand Up @@ -449,7 +448,7 @@ int main(int argc, char **argv, char * const *envp) {
RTDyldMemoryManager *RTDyldMM = nullptr;
if (!ForceInterpreter) {
if (RemoteMCJIT)
RTDyldMM = new RemoteMemoryManager();
RTDyldMM = new ForwardingMemoryManager();
else
RTDyldMM = new SectionMemoryManager();

Expand Down Expand Up @@ -582,6 +581,27 @@ int main(int argc, char **argv, char * const *envp) {

int Result;

// Sanity check use of remote-jit: LLI currently only supports use of the
// remote JIT on Unix platforms.
// FIXME: Remove this pointless fallback mode which causes tests to "pass"
// on platforms where they should XFAIL.
if (RemoteMCJIT) {
#ifndef LLVM_ON_UNIX
errs() << "Warning: host does not support external remote targets.\n"
<< " Defaulting to local execution execution\n";
RemoteMCJIT = false;
#else
if (ChildExecPath.empty()) {
errs() << "-remote-mcjit requires -mcjit-remote-process.\n";
exit(1);
} else if (!sys::fs::can_execute(ChildExecPath)) {
errs() << "Unable to find usable child executable: '" << ChildExecPath
<< "'\n";
return -1;
}
#endif
}

if (!RemoteMCJIT) {
// If the program doesn't explicitly call exit, we will need the Exit
// function later on to make an explicit call, so get the function now.
Expand Down Expand Up @@ -629,66 +649,123 @@ int main(int argc, char **argv, char * const *envp) {
// Remote target MCJIT doesn't (yet) support static constructors. No reason
// it couldn't. This is a limitation of the LLI implemantation, not the
// MCJIT itself. FIXME.
//
RemoteMemoryManager *MM = static_cast<RemoteMemoryManager*>(RTDyldMM);
// Everything is prepared now, so lay out our program for the target
// address space, assign the section addresses to resolve any relocations,
// and send it to the target.

std::unique_ptr<RemoteTarget> Target;
if (!ChildExecPath.empty()) { // Remote execution on a child process
#ifndef LLVM_ON_UNIX
// FIXME: Remove this pointless fallback mode which causes tests to "pass"
// on platforms where they should XFAIL.
errs() << "Warning: host does not support external remote targets.\n"
<< " Defaulting to simulated remote execution\n";
Target.reset(new RemoteTarget);
#else
if (!sys::fs::can_execute(ChildExecPath)) {
errs() << "Unable to find usable child executable: '" << ChildExecPath
<< "'\n";
return -1;
}
Target.reset(new RemoteTargetExternal(ChildExecPath));
#endif
} else {
// No child process name provided, use simulated remote execution.
Target.reset(new RemoteTarget);

// Lanch the remote process and get a channel to it.
std::unique_ptr<FDRPCChannel> C = launchRemote();
if (!C) {
errs() << "Failed to launch remote JIT.\n";
exit(1);
}

// Give the memory manager a pointer to our remote target interface object.
MM->setRemoteTarget(Target.get());
// Create a remote target client running over the channel.
typedef orc::remote::OrcRemoteTargetClient<orc::remote::RPCChannel> MyRemote;
ErrorOr<MyRemote> R = MyRemote::Create(*C);
if (!R) {
errs() << "Could not create remote: " << R.getError().message() << "\n";
exit(1);
}

// Create the remote target.
if (!Target->create()) {
errs() << "ERROR: " << Target->getErrorMsg() << "\n";
return EXIT_FAILURE;
// Create a remote memory manager.
std::unique_ptr<MyRemote::RCMemoryManager> RemoteMM;
if (auto EC = R->createRemoteMemoryManager(RemoteMM)) {
errs() << "Could not create remote memory manager: " << EC.message() << "\n";
exit(1);
}

// Since we're executing in a (at least simulated) remote address space,
// we can't use the ExecutionEngine::runFunctionAsMain(). We have to
// grab the function address directly here and tell the remote target
// to execute the function.
//
// Our memory manager will map generated code into the remote address
// space as it is loaded and copy the bits over during the finalizeMemory
// operation.
//
// Forward MCJIT's memory manager calls to the remote memory manager.
static_cast<ForwardingMemoryManager*>(RTDyldMM)->setMemMgr(
std::move(RemoteMM));

// Forward MCJIT's symbol resolution calls to the remote.
static_cast<ForwardingMemoryManager*>(RTDyldMM)->setResolver(
orc::createLambdaResolver(
[&](const std::string &Name) {
orc::TargetAddress Addr = 0;
if (auto EC = R->getSymbolAddress(Addr, Name)) {
errs() << "Failure during symbol lookup: " << EC.message() << "\n";
exit(1);
}
return RuntimeDyld::SymbolInfo(Addr, JITSymbolFlags::Exported);
},
[](const std::string &Name) { return nullptr; }
));

// Grab the target address of the JIT'd main function on the remote and call
// it.
// FIXME: argv and envp handling.
uint64_t Entry = EE->getFunctionAddress(EntryFn->getName().str());

orc::TargetAddress Entry = EE->getFunctionAddress(EntryFn->getName().str());
EE->finalizeObject();
DEBUG(dbgs() << "Executing '" << EntryFn->getName() << "' at 0x"
<< format("%llx", Entry) << "\n");

if (!Target->executeCode(Entry, Result))
errs() << "ERROR: " << Target->getErrorMsg() << "\n";
if (auto EC = R->callIntVoid(Result, Entry))
errs() << "ERROR: " << EC.message() << "\n";

// Like static constructors, the remote target MCJIT support doesn't handle
// this yet. It could. FIXME.

// Stop the remote target
Target->stop();
// Delete the EE - we need to tear it down *before* we terminate the session
// with the remote, otherwise it'll crash when it tries to release resources
// on a remote that has already been disconnected.
delete EE;
EE = nullptr;

// Signal the remote target that we're done JITing.
R->terminateSession();
}

return Result;
}

std::unique_ptr<FDRPCChannel> launchRemote() {
#ifndef LLVM_ON_UNIX
llvm_unreachable("launchRemote not supported on non-Unix platforms");
#else
int PipeFD[2][2];
pid_t ChildPID;

// Create two pipes.
if (pipe(PipeFD[0]) != 0 || pipe(PipeFD[1]) != 0)
perror("Error creating pipe: ");

ChildPID = fork();

if (ChildPID == 0) {
// In the child...

// Close the parent ends of the pipes
close(PipeFD[0][1]);
close(PipeFD[1][0]);


// Execute the child process.
std::unique_ptr<char[]> ChildPath, ChildIn, ChildOut;
{
ChildPath.reset(new char[ChildExecPath.size() + 1]);
std::copy(ChildExecPath.begin(), ChildExecPath.end(), &ChildPath[0]);
ChildPath[ChildExecPath.size()] = '\0';
std::string ChildInStr = std::to_string(PipeFD[0][0]);
ChildIn.reset(new char[ChildInStr.size() + 1]);
std::copy(ChildInStr.begin(), ChildInStr.end(), &ChildIn[0]);
ChildIn[ChildInStr.size()] = '\0';
std::string ChildOutStr = std::to_string(PipeFD[1][1]);
ChildOut.reset(new char[ChildOutStr.size() + 1]);
std::copy(ChildOutStr.begin(), ChildOutStr.end(), &ChildOut[0]);
ChildOut[ChildOutStr.size()] = '\0';
}

char * const args[] = { &ChildPath[0], &ChildIn[0], &ChildOut[0], nullptr };
int rc = execv(ChildExecPath.c_str(), args);
if (rc != 0)
perror("Error executing child process: ");
llvm_unreachable("Error executing child process");
}
// else we're the parent...

// Close the child ends of the pipes
close(PipeFD[0][0]);
close(PipeFD[1][1]);

// Return an RPC channel connected to our end of the pipes.
return llvm::make_unique<FDRPCChannel>(PipeFD[1][0], PipeFD[0][1]);
#endif
}