| //===- OrcRemoteTargetServer.h - Orc Remote-target Server -------*- 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file defines the OrcRemoteTargetServer class. It can be used to build a |
| // JIT server that can execute code sent from an OrcRemoteTargetClient. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_EXECUTIONENGINE_ORC_ORCREMOTETARGETSERVER_H |
| #define LLVM_EXECUTIONENGINE_ORC_ORCREMOTETARGETSERVER_H |
| |
| #include "llvm/ExecutionEngine/JITSymbol.h" |
| #include "llvm/ExecutionEngine/Orc/OrcError.h" |
| #include "llvm/ExecutionEngine/Orc/OrcRemoteTargetRPCAPI.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/Format.h" |
| #include "llvm/Support/Host.h" |
| #include "llvm/Support/Memory.h" |
| #include "llvm/Support/Process.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <algorithm> |
| #include <cassert> |
| #include <cstddef> |
| #include <cstdint> |
| #include <functional> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <system_error> |
| #include <tuple> |
| #include <type_traits> |
| #include <vector> |
| |
| #define DEBUG_TYPE "orc-remote" |
| |
| namespace llvm { |
| namespace orc { |
| namespace remote { |
| |
| template <typename ChannelT, typename TargetT> |
| class OrcRemoteTargetServer |
| : public rpc::SingleThreadedRPCEndpoint<rpc::RawByteChannel> { |
| public: |
| using SymbolLookupFtor = |
| std::function<JITTargetAddress(const std::string &Name)>; |
| |
| using EHFrameRegistrationFtor = |
| std::function<void(uint8_t *Addr, uint32_t Size)>; |
| |
| OrcRemoteTargetServer(ChannelT &Channel, SymbolLookupFtor SymbolLookup, |
| EHFrameRegistrationFtor EHFramesRegister, |
| EHFrameRegistrationFtor EHFramesDeregister) |
| : rpc::SingleThreadedRPCEndpoint<rpc::RawByteChannel>(Channel, true), |
| SymbolLookup(std::move(SymbolLookup)), |
| EHFramesRegister(std::move(EHFramesRegister)), |
| EHFramesDeregister(std::move(EHFramesDeregister)) { |
| using ThisT = typename std::remove_reference<decltype(*this)>::type; |
| addHandler<exec::CallIntVoid>(*this, &ThisT::handleCallIntVoid); |
| addHandler<exec::CallMain>(*this, &ThisT::handleCallMain); |
| addHandler<exec::CallVoidVoid>(*this, &ThisT::handleCallVoidVoid); |
| addHandler<mem::CreateRemoteAllocator>(*this, |
| &ThisT::handleCreateRemoteAllocator); |
| addHandler<mem::DestroyRemoteAllocator>( |
| *this, &ThisT::handleDestroyRemoteAllocator); |
| addHandler<mem::ReadMem>(*this, &ThisT::handleReadMem); |
| addHandler<mem::ReserveMem>(*this, &ThisT::handleReserveMem); |
| addHandler<mem::SetProtections>(*this, &ThisT::handleSetProtections); |
| addHandler<mem::WriteMem>(*this, &ThisT::handleWriteMem); |
| addHandler<mem::WritePtr>(*this, &ThisT::handleWritePtr); |
| addHandler<eh::RegisterEHFrames>(*this, &ThisT::handleRegisterEHFrames); |
| addHandler<eh::DeregisterEHFrames>(*this, &ThisT::handleDeregisterEHFrames); |
| addHandler<stubs::CreateIndirectStubsOwner>( |
| *this, &ThisT::handleCreateIndirectStubsOwner); |
| addHandler<stubs::DestroyIndirectStubsOwner>( |
| *this, &ThisT::handleDestroyIndirectStubsOwner); |
| addHandler<stubs::EmitIndirectStubs>(*this, |
| &ThisT::handleEmitIndirectStubs); |
| addHandler<stubs::EmitResolverBlock>(*this, |
| &ThisT::handleEmitResolverBlock); |
| addHandler<stubs::EmitTrampolineBlock>(*this, |
| &ThisT::handleEmitTrampolineBlock); |
| addHandler<utils::GetSymbolAddress>(*this, &ThisT::handleGetSymbolAddress); |
| addHandler<utils::GetRemoteInfo>(*this, &ThisT::handleGetRemoteInfo); |
| addHandler<utils::TerminateSession>(*this, &ThisT::handleTerminateSession); |
| } |
| |
| // FIXME: Remove move/copy ops once MSVC supports synthesizing move ops. |
| OrcRemoteTargetServer(const OrcRemoteTargetServer &) = delete; |
| OrcRemoteTargetServer &operator=(const OrcRemoteTargetServer &) = delete; |
| |
| OrcRemoteTargetServer(OrcRemoteTargetServer &&Other) = default; |
| OrcRemoteTargetServer &operator=(OrcRemoteTargetServer &&) = delete; |
| |
| Expected<JITTargetAddress> requestCompile(JITTargetAddress TrampolineAddr) { |
| return callB<utils::RequestCompile>(TrampolineAddr); |
| } |
| |
| bool receivedTerminate() const { return TerminateFlag; } |
| |
| private: |
| struct Allocator { |
| Allocator() = default; |
| Allocator(Allocator &&Other) : Allocs(std::move(Other.Allocs)) {} |
| |
| Allocator &operator=(Allocator &&Other) { |
| Allocs = std::move(Other.Allocs); |
| return *this; |
| } |
| |
| ~Allocator() { |
| for (auto &Alloc : Allocs) |
| sys::Memory::releaseMappedMemory(Alloc.second); |
| } |
| |
| Error allocate(void *&Addr, size_t Size, uint32_t Align) { |
| std::error_code EC; |
| sys::MemoryBlock MB = sys::Memory::allocateMappedMemory( |
| Size, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC); |
| if (EC) |
| return errorCodeToError(EC); |
| |
| Addr = MB.base(); |
| assert(Allocs.find(MB.base()) == Allocs.end() && "Duplicate alloc"); |
| Allocs[MB.base()] = std::move(MB); |
| return Error::success(); |
| } |
| |
| Error setProtections(void *block, unsigned Flags) { |
| auto I = Allocs.find(block); |
| if (I == Allocs.end()) |
| return errorCodeToError(orcError(OrcErrorCode::RemoteMProtectAddrUnrecognized)); |
| return errorCodeToError( |
| sys::Memory::protectMappedMemory(I->second, Flags)); |
| } |
| |
| private: |
| std::map<void *, sys::MemoryBlock> Allocs; |
| }; |
| |
| static Error doNothing() { return Error::success(); } |
| |
| static JITTargetAddress reenter(void *JITTargetAddr, void *TrampolineAddr) { |
| auto T = static_cast<OrcRemoteTargetServer *>(JITTargetAddr); |
| auto AddrOrErr = T->requestCompile(static_cast<JITTargetAddress>( |
| reinterpret_cast<uintptr_t>(TrampolineAddr))); |
| // FIXME: Allow customizable failure substitution functions. |
| assert(AddrOrErr && "Compile request failed"); |
| return *AddrOrErr; |
| } |
| |
| Expected<int32_t> handleCallIntVoid(JITTargetAddress Addr) { |
| using IntVoidFnTy = int (*)(); |
| |
| IntVoidFnTy Fn = |
| reinterpret_cast<IntVoidFnTy>(static_cast<uintptr_t>(Addr)); |
| |
| LLVM_DEBUG(dbgs() << " Calling " << format("0x%016x", Addr) << "\n"); |
| int Result = Fn(); |
| LLVM_DEBUG(dbgs() << " Result = " << Result << "\n"); |
| |
| return Result; |
| } |
| |
| Expected<int32_t> handleCallMain(JITTargetAddress Addr, |
| std::vector<std::string> Args) { |
| using MainFnTy = int (*)(int, const char *[]); |
| |
| MainFnTy Fn = reinterpret_cast<MainFnTy>(static_cast<uintptr_t>(Addr)); |
| int ArgC = Args.size() + 1; |
| int Idx = 1; |
| std::unique_ptr<const char *[]> ArgV(new const char *[ArgC + 1]); |
| ArgV[0] = "<jit process>"; |
| for (auto &Arg : Args) |
| ArgV[Idx++] = Arg.c_str(); |
| ArgV[ArgC] = 0; |
| LLVM_DEBUG(for (int Idx = 0; Idx < ArgC; ++Idx) { |
| llvm::dbgs() << "Arg " << Idx << ": " << ArgV[Idx] << "\n"; |
| }); |
| |
| LLVM_DEBUG(dbgs() << " Calling " << format("0x%016x", Addr) << "\n"); |
| int Result = Fn(ArgC, ArgV.get()); |
| LLVM_DEBUG(dbgs() << " Result = " << Result << "\n"); |
| |
| return Result; |
| } |
| |
| Error handleCallVoidVoid(JITTargetAddress Addr) { |
| using VoidVoidFnTy = void (*)(); |
| |
| VoidVoidFnTy Fn = |
| reinterpret_cast<VoidVoidFnTy>(static_cast<uintptr_t>(Addr)); |
| |
| LLVM_DEBUG(dbgs() << " Calling " << format("0x%016x", Addr) << "\n"); |
| Fn(); |
| LLVM_DEBUG(dbgs() << " Complete.\n"); |
| |
| return Error::success(); |
| } |
| |
| Error handleCreateRemoteAllocator(ResourceIdMgr::ResourceId Id) { |
| auto I = Allocators.find(Id); |
| if (I != Allocators.end()) |
| return errorCodeToError( |
| orcError(OrcErrorCode::RemoteAllocatorIdAlreadyInUse)); |
| LLVM_DEBUG(dbgs() << " Created allocator " << Id << "\n"); |
| Allocators[Id] = Allocator(); |
| return Error::success(); |
| } |
| |
| Error handleCreateIndirectStubsOwner(ResourceIdMgr::ResourceId Id) { |
| auto I = IndirectStubsOwners.find(Id); |
| if (I != IndirectStubsOwners.end()) |
| return errorCodeToError( |
| orcError(OrcErrorCode::RemoteIndirectStubsOwnerIdAlreadyInUse)); |
| LLVM_DEBUG(dbgs() << " Create indirect stubs owner " << Id << "\n"); |
| IndirectStubsOwners[Id] = ISBlockOwnerList(); |
| return Error::success(); |
| } |
| |
| Error handleDeregisterEHFrames(JITTargetAddress TAddr, uint32_t Size) { |
| uint8_t *Addr = reinterpret_cast<uint8_t *>(static_cast<uintptr_t>(TAddr)); |
| LLVM_DEBUG(dbgs() << " Registering EH frames at " |
| << format("0x%016x", TAddr) << ", Size = " << Size |
| << " bytes\n"); |
| EHFramesDeregister(Addr, Size); |
| return Error::success(); |
| } |
| |
| Error handleDestroyRemoteAllocator(ResourceIdMgr::ResourceId Id) { |
| auto I = Allocators.find(Id); |
| if (I == Allocators.end()) |
| return errorCodeToError( |
| orcError(OrcErrorCode::RemoteAllocatorDoesNotExist)); |
| Allocators.erase(I); |
| LLVM_DEBUG(dbgs() << " Destroyed allocator " << Id << "\n"); |
| return Error::success(); |
| } |
| |
| Error handleDestroyIndirectStubsOwner(ResourceIdMgr::ResourceId Id) { |
| auto I = IndirectStubsOwners.find(Id); |
| if (I == IndirectStubsOwners.end()) |
| return errorCodeToError( |
| orcError(OrcErrorCode::RemoteIndirectStubsOwnerDoesNotExist)); |
| IndirectStubsOwners.erase(I); |
| return Error::success(); |
| } |
| |
| Expected<std::tuple<JITTargetAddress, JITTargetAddress, uint32_t>> |
| handleEmitIndirectStubs(ResourceIdMgr::ResourceId Id, |
| uint32_t NumStubsRequired) { |
| LLVM_DEBUG(dbgs() << " ISMgr " << Id << " request " << NumStubsRequired |
| << " stubs.\n"); |
| |
| auto StubOwnerItr = IndirectStubsOwners.find(Id); |
| if (StubOwnerItr == IndirectStubsOwners.end()) |
| return errorCodeToError( |
| orcError(OrcErrorCode::RemoteIndirectStubsOwnerDoesNotExist)); |
| |
| typename TargetT::IndirectStubsInfo IS; |
| if (auto Err = |
| TargetT::emitIndirectStubsBlock(IS, NumStubsRequired, nullptr)) |
| return std::move(Err); |
| |
| JITTargetAddress StubsBase = static_cast<JITTargetAddress>( |
| reinterpret_cast<uintptr_t>(IS.getStub(0))); |
| JITTargetAddress PtrsBase = static_cast<JITTargetAddress>( |
| reinterpret_cast<uintptr_t>(IS.getPtr(0))); |
| uint32_t NumStubsEmitted = IS.getNumStubs(); |
| |
| auto &BlockList = StubOwnerItr->second; |
| BlockList.push_back(std::move(IS)); |
| |
| return std::make_tuple(StubsBase, PtrsBase, NumStubsEmitted); |
| } |
| |
| Error handleEmitResolverBlock() { |
| std::error_code EC; |
| ResolverBlock = sys::OwningMemoryBlock(sys::Memory::allocateMappedMemory( |
| TargetT::ResolverCodeSize, nullptr, |
| sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC)); |
| if (EC) |
| return errorCodeToError(EC); |
| |
| TargetT::writeResolverCode(static_cast<uint8_t *>(ResolverBlock.base()), |
| &reenter, this); |
| |
| return errorCodeToError(sys::Memory::protectMappedMemory( |
| ResolverBlock.getMemoryBlock(), |
| sys::Memory::MF_READ | sys::Memory::MF_EXEC)); |
| } |
| |
| Expected<std::tuple<JITTargetAddress, uint32_t>> handleEmitTrampolineBlock() { |
| std::error_code EC; |
| auto TrampolineBlock = |
| sys::OwningMemoryBlock(sys::Memory::allocateMappedMemory( |
| sys::Process::getPageSizeEstimate(), nullptr, |
| sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC)); |
| if (EC) |
| return errorCodeToError(EC); |
| |
| uint32_t NumTrampolines = |
| (sys::Process::getPageSizeEstimate() - TargetT::PointerSize) / |
| TargetT::TrampolineSize; |
| |
| uint8_t *TrampolineMem = static_cast<uint8_t *>(TrampolineBlock.base()); |
| TargetT::writeTrampolines(TrampolineMem, ResolverBlock.base(), |
| NumTrampolines); |
| |
| EC = sys::Memory::protectMappedMemory(TrampolineBlock.getMemoryBlock(), |
| sys::Memory::MF_READ | |
| sys::Memory::MF_EXEC); |
| |
| TrampolineBlocks.push_back(std::move(TrampolineBlock)); |
| |
| auto TrampolineBaseAddr = static_cast<JITTargetAddress>( |
| reinterpret_cast<uintptr_t>(TrampolineMem)); |
| |
| return std::make_tuple(TrampolineBaseAddr, NumTrampolines); |
| } |
| |
| Expected<JITTargetAddress> handleGetSymbolAddress(const std::string &Name) { |
| JITTargetAddress Addr = SymbolLookup(Name); |
| LLVM_DEBUG(dbgs() << " Symbol '" << Name |
| << "' = " << format("0x%016x", Addr) << "\n"); |
| return Addr; |
| } |
| |
| Expected<std::tuple<std::string, uint32_t, uint32_t, uint32_t, uint32_t>> |
| handleGetRemoteInfo() { |
| std::string ProcessTriple = sys::getProcessTriple(); |
| uint32_t PointerSize = TargetT::PointerSize; |
| uint32_t PageSize = sys::Process::getPageSizeEstimate(); |
| uint32_t TrampolineSize = TargetT::TrampolineSize; |
| uint32_t IndirectStubSize = TargetT::IndirectStubsInfo::StubSize; |
| LLVM_DEBUG(dbgs() << " Remote info:\n" |
| << " triple = '" << ProcessTriple << "'\n" |
| << " pointer size = " << PointerSize << "\n" |
| << " page size = " << PageSize << "\n" |
| << " trampoline size = " << TrampolineSize << "\n" |
| << " indirect stub size = " << IndirectStubSize |
| << "\n"); |
| return std::make_tuple(ProcessTriple, PointerSize, PageSize, TrampolineSize, |
| IndirectStubSize); |
| } |
| |
| Expected<std::vector<uint8_t>> handleReadMem(JITTargetAddress RSrc, |
| uint64_t Size) { |
| uint8_t *Src = reinterpret_cast<uint8_t *>(static_cast<uintptr_t>(RSrc)); |
| |
| LLVM_DEBUG(dbgs() << " Reading " << Size << " bytes from " |
| << format("0x%016x", RSrc) << "\n"); |
| |
| std::vector<uint8_t> Buffer; |
| Buffer.resize(Size); |
| for (uint8_t *P = Src; Size != 0; --Size) |
| Buffer.push_back(*P++); |
| |
| return Buffer; |
| } |
| |
| Error handleRegisterEHFrames(JITTargetAddress TAddr, uint32_t Size) { |
| uint8_t *Addr = reinterpret_cast<uint8_t *>(static_cast<uintptr_t>(TAddr)); |
| LLVM_DEBUG(dbgs() << " Registering EH frames at " |
| << format("0x%016x", TAddr) << ", Size = " << Size |
| << " bytes\n"); |
| EHFramesRegister(Addr, Size); |
| return Error::success(); |
| } |
| |
| Expected<JITTargetAddress> handleReserveMem(ResourceIdMgr::ResourceId Id, |
| uint64_t Size, uint32_t Align) { |
| auto I = Allocators.find(Id); |
| if (I == Allocators.end()) |
| return errorCodeToError( |
| orcError(OrcErrorCode::RemoteAllocatorDoesNotExist)); |
| auto &Allocator = I->second; |
| void *LocalAllocAddr = nullptr; |
| if (auto Err = Allocator.allocate(LocalAllocAddr, Size, Align)) |
| return std::move(Err); |
| |
| LLVM_DEBUG(dbgs() << " Allocator " << Id << " reserved " << LocalAllocAddr |
| << " (" << Size << " bytes, alignment " << Align |
| << ")\n"); |
| |
| JITTargetAddress AllocAddr = static_cast<JITTargetAddress>( |
| reinterpret_cast<uintptr_t>(LocalAllocAddr)); |
| |
| return AllocAddr; |
| } |
| |
| Error handleSetProtections(ResourceIdMgr::ResourceId Id, |
| JITTargetAddress Addr, uint32_t Flags) { |
| auto I = Allocators.find(Id); |
| if (I == Allocators.end()) |
| return errorCodeToError( |
| orcError(OrcErrorCode::RemoteAllocatorDoesNotExist)); |
| auto &Allocator = I->second; |
| void *LocalAddr = reinterpret_cast<void *>(static_cast<uintptr_t>(Addr)); |
| LLVM_DEBUG(dbgs() << " Allocator " << Id << " set permissions on " |
| << LocalAddr << " to " |
| << (Flags & sys::Memory::MF_READ ? 'R' : '-') |
| << (Flags & sys::Memory::MF_WRITE ? 'W' : '-') |
| << (Flags & sys::Memory::MF_EXEC ? 'X' : '-') << "\n"); |
| return Allocator.setProtections(LocalAddr, Flags); |
| } |
| |
| Error handleTerminateSession() { |
| TerminateFlag = true; |
| return Error::success(); |
| } |
| |
| Error handleWriteMem(DirectBufferWriter DBW) { |
| LLVM_DEBUG(dbgs() << " Writing " << DBW.getSize() << " bytes to " |
| << format("0x%016x", DBW.getDst()) << "\n"); |
| return Error::success(); |
| } |
| |
| Error handleWritePtr(JITTargetAddress Addr, JITTargetAddress PtrVal) { |
| LLVM_DEBUG(dbgs() << " Writing pointer *" << format("0x%016x", Addr) |
| << " = " << format("0x%016x", PtrVal) << "\n"); |
| uintptr_t *Ptr = |
| reinterpret_cast<uintptr_t *>(static_cast<uintptr_t>(Addr)); |
| *Ptr = static_cast<uintptr_t>(PtrVal); |
| return Error::success(); |
| } |
| |
| SymbolLookupFtor SymbolLookup; |
| EHFrameRegistrationFtor EHFramesRegister, EHFramesDeregister; |
| std::map<ResourceIdMgr::ResourceId, Allocator> Allocators; |
| using ISBlockOwnerList = std::vector<typename TargetT::IndirectStubsInfo>; |
| std::map<ResourceIdMgr::ResourceId, ISBlockOwnerList> IndirectStubsOwners; |
| sys::OwningMemoryBlock ResolverBlock; |
| std::vector<sys::OwningMemoryBlock> TrampolineBlocks; |
| bool TerminateFlag = false; |
| }; |
| |
| } // end namespace remote |
| } // end namespace orc |
| } // end namespace llvm |
| |
| #undef DEBUG_TYPE |
| |
| #endif // LLVM_EXECUTIONENGINE_ORC_ORCREMOTETARGETSERVER_H |