//===- IndirectionUtils.h - Utilities for adding indirections ---*- 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 // //===----------------------------------------------------------------------===// // // Contains utilities for adding indirections and breaking up modules. // //===----------------------------------------------------------------------===// #ifndef LLVM_EXECUTIONENGINE_ORC_INDIRECTIONUTILS_H #define LLVM_EXECUTIONENGINE_ORC_INDIRECTIONUTILS_H #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/ExecutionEngine/JITSymbol.h" #include "llvm/ExecutionEngine/Orc/Core.h" #include "llvm/Support/Error.h" #include "llvm/Support/Memory.h" #include "llvm/Support/Process.h" #include "llvm/Transforms/Utils/ValueMapper.h" #include #include #include #include #include #include #include #include #include namespace llvm { class Constant; class Function; class FunctionType; class GlobalAlias; class GlobalVariable; class Module; class PointerType; class Triple; class Value; namespace orc { /// Base class for pools of compiler re-entry trampolines. /// These trampolines are callable addresses that save all register state /// before calling a supplied function to return the trampoline landing /// address, then restore all state before jumping to that address. They /// are used by various ORC APIs to support lazy compilation class TrampolinePool { public: virtual ~TrampolinePool() {} /// Get an available trampoline address. /// Returns an error if no trampoline can be created. virtual Expected getTrampoline() = 0; private: virtual void anchor(); }; /// A trampoline pool for trampolines within the current process. template class LocalTrampolinePool : public TrampolinePool { public: using GetTrampolineLandingFunction = std::function; /// Creates a LocalTrampolinePool with the given RunCallback function. /// Returns an error if this function is unable to correctly allocate, write /// and protect the resolver code block. static Expected> Create(GetTrampolineLandingFunction GetTrampolineLanding) { Error Err = Error::success(); auto LTP = std::unique_ptr( new LocalTrampolinePool(std::move(GetTrampolineLanding), Err)); if (Err) return std::move(Err); return std::move(LTP); } /// Get a free trampoline. Returns an error if one can not be provide (e.g. /// because the pool is empty and can not be grown). Expected getTrampoline() override { std::lock_guard Lock(LTPMutex); if (AvailableTrampolines.empty()) { if (auto Err = grow()) return std::move(Err); } assert(!AvailableTrampolines.empty() && "Failed to grow trampoline pool"); auto TrampolineAddr = AvailableTrampolines.back(); AvailableTrampolines.pop_back(); return TrampolineAddr; } /// Returns the given trampoline to the pool for re-use. void releaseTrampoline(JITTargetAddress TrampolineAddr) { std::lock_guard Lock(LTPMutex); AvailableTrampolines.push_back(TrampolineAddr); } private: static JITTargetAddress reenter(void *TrampolinePoolPtr, void *TrampolineId) { LocalTrampolinePool *TrampolinePool = static_cast(TrampolinePoolPtr); return TrampolinePool->GetTrampolineLanding(static_cast( reinterpret_cast(TrampolineId))); } LocalTrampolinePool(GetTrampolineLandingFunction GetTrampolineLanding, Error &Err) : GetTrampolineLanding(std::move(GetTrampolineLanding)) { ErrorAsOutParameter _(&Err); /// Try to set up the resolver block. std::error_code EC; ResolverBlock = sys::OwningMemoryBlock(sys::Memory::allocateMappedMemory( ORCABI::ResolverCodeSize, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC)); if (EC) { Err = errorCodeToError(EC); return; } ORCABI::writeResolverCode(static_cast(ResolverBlock.base()), &reenter, this); EC = sys::Memory::protectMappedMemory(ResolverBlock.getMemoryBlock(), sys::Memory::MF_READ | sys::Memory::MF_EXEC); if (EC) { Err = errorCodeToError(EC); return; } } Error grow() { assert(this->AvailableTrampolines.empty() && "Growing prematurely?"); 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); unsigned NumTrampolines = (sys::Process::getPageSizeEstimate() - ORCABI::PointerSize) / ORCABI::TrampolineSize; uint8_t *TrampolineMem = static_cast(TrampolineBlock.base()); ORCABI::writeTrampolines(TrampolineMem, ResolverBlock.base(), NumTrampolines); for (unsigned I = 0; I < NumTrampolines; ++I) this->AvailableTrampolines.push_back( static_cast(reinterpret_cast( TrampolineMem + (I * ORCABI::TrampolineSize)))); if (auto EC = sys::Memory::protectMappedMemory( TrampolineBlock.getMemoryBlock(), sys::Memory::MF_READ | sys::Memory::MF_EXEC)) return errorCodeToError(EC); TrampolineBlocks.push_back(std::move(TrampolineBlock)); return Error::success(); } GetTrampolineLandingFunction GetTrampolineLanding; std::mutex LTPMutex; sys::OwningMemoryBlock ResolverBlock; std::vector TrampolineBlocks; std::vector AvailableTrampolines; }; /// Target-independent base class for compile callback management. class JITCompileCallbackManager { public: using CompileFunction = std::function; virtual ~JITCompileCallbackManager() = default; /// Reserve a compile callback. Expected getCompileCallback(CompileFunction Compile); /// Execute the callback for the given trampoline id. Called by the JIT /// to compile functions on demand. JITTargetAddress executeCompileCallback(JITTargetAddress TrampolineAddr); protected: /// Construct a JITCompileCallbackManager. JITCompileCallbackManager(std::unique_ptr TP, ExecutionSession &ES, JITTargetAddress ErrorHandlerAddress) : TP(std::move(TP)), ES(ES), CallbacksJD(ES.createJITDylib("")), ErrorHandlerAddress(ErrorHandlerAddress) {} void setTrampolinePool(std::unique_ptr TP) { this->TP = std::move(TP); } private: std::mutex CCMgrMutex; std::unique_ptr TP; ExecutionSession &ES; JITDylib &CallbacksJD; JITTargetAddress ErrorHandlerAddress; std::map AddrToSymbol; size_t NextCallbackId = 0; }; /// Manage compile callbacks for in-process JITs. template class LocalJITCompileCallbackManager : public JITCompileCallbackManager { public: /// Create a new LocalJITCompileCallbackManager. static Expected> Create(ExecutionSession &ES, JITTargetAddress ErrorHandlerAddress) { Error Err = Error::success(); auto CCMgr = std::unique_ptr( new LocalJITCompileCallbackManager(ES, ErrorHandlerAddress, Err)); if (Err) return std::move(Err); return std::move(CCMgr); } private: /// Construct a InProcessJITCompileCallbackManager. /// @param ErrorHandlerAddress The address of an error handler in the target /// process to be used if a compile callback fails. LocalJITCompileCallbackManager(ExecutionSession &ES, JITTargetAddress ErrorHandlerAddress, Error &Err) : JITCompileCallbackManager(nullptr, ES, ErrorHandlerAddress) { ErrorAsOutParameter _(&Err); auto TP = LocalTrampolinePool::Create( [this](JITTargetAddress TrampolineAddr) { return executeCompileCallback(TrampolineAddr); }); if (!TP) { Err = TP.takeError(); return; } setTrampolinePool(std::move(*TP)); } }; /// Base class for managing collections of named indirect stubs. class IndirectStubsManager { public: /// Map type for initializing the manager. See init. using StubInitsMap = StringMap>; virtual ~IndirectStubsManager() = default; /// Create a single stub with the given name, target address and flags. virtual Error createStub(StringRef StubName, JITTargetAddress StubAddr, JITSymbolFlags StubFlags) = 0; /// Create StubInits.size() stubs with the given names, target /// addresses, and flags. virtual Error createStubs(const StubInitsMap &StubInits) = 0; /// Find the stub with the given name. If ExportedStubsOnly is true, /// this will only return a result if the stub's flags indicate that it /// is exported. virtual JITEvaluatedSymbol findStub(StringRef Name, bool ExportedStubsOnly) = 0; /// Find the implementation-pointer for the stub. virtual JITEvaluatedSymbol findPointer(StringRef Name) = 0; /// Change the value of the implementation pointer for the stub. virtual Error updatePointer(StringRef Name, JITTargetAddress NewAddr) = 0; private: virtual void anchor(); }; /// IndirectStubsManager implementation for the host architecture, e.g. /// OrcX86_64. (See OrcArchitectureSupport.h). template class LocalIndirectStubsManager : public IndirectStubsManager { public: Error createStub(StringRef StubName, JITTargetAddress StubAddr, JITSymbolFlags StubFlags) override { std::lock_guard Lock(StubsMutex); if (auto Err = reserveStubs(1)) return Err; createStubInternal(StubName, StubAddr, StubFlags); return Error::success(); } Error createStubs(const StubInitsMap &StubInits) override { std::lock_guard Lock(StubsMutex); if (auto Err = reserveStubs(StubInits.size())) return Err; for (auto &Entry : StubInits) createStubInternal(Entry.first(), Entry.second.first, Entry.second.second); return Error::success(); } JITEvaluatedSymbol findStub(StringRef Name, bool ExportedStubsOnly) override { std::lock_guard Lock(StubsMutex); auto I = StubIndexes.find(Name); if (I == StubIndexes.end()) return nullptr; auto Key = I->second.first; void *StubAddr = IndirectStubsInfos[Key.first].getStub(Key.second); assert(StubAddr && "Missing stub address"); auto StubTargetAddr = static_cast(reinterpret_cast(StubAddr)); auto StubSymbol = JITEvaluatedSymbol(StubTargetAddr, I->second.second); if (ExportedStubsOnly && !StubSymbol.getFlags().isExported()) return nullptr; return StubSymbol; } JITEvaluatedSymbol findPointer(StringRef Name) override { std::lock_guard Lock(StubsMutex); auto I = StubIndexes.find(Name); if (I == StubIndexes.end()) return nullptr; auto Key = I->second.first; void *PtrAddr = IndirectStubsInfos[Key.first].getPtr(Key.second); assert(PtrAddr && "Missing pointer address"); auto PtrTargetAddr = static_cast(reinterpret_cast(PtrAddr)); return JITEvaluatedSymbol(PtrTargetAddr, I->second.second); } Error updatePointer(StringRef Name, JITTargetAddress NewAddr) override { using AtomicIntPtr = std::atomic; std::lock_guard Lock(StubsMutex); auto I = StubIndexes.find(Name); assert(I != StubIndexes.end() && "No stub pointer for symbol"); auto Key = I->second.first; AtomicIntPtr *AtomicStubPtr = reinterpret_cast( IndirectStubsInfos[Key.first].getPtr(Key.second)); *AtomicStubPtr = static_cast(NewAddr); return Error::success(); } private: Error reserveStubs(unsigned NumStubs) { if (NumStubs <= FreeStubs.size()) return Error::success(); unsigned NewStubsRequired = NumStubs - FreeStubs.size(); unsigned NewBlockId = IndirectStubsInfos.size(); typename TargetT::IndirectStubsInfo ISI; if (auto Err = TargetT::emitIndirectStubsBlock(ISI, NewStubsRequired, nullptr)) return Err; for (unsigned I = 0; I < ISI.getNumStubs(); ++I) FreeStubs.push_back(std::make_pair(NewBlockId, I)); IndirectStubsInfos.push_back(std::move(ISI)); return Error::success(); } void createStubInternal(StringRef StubName, JITTargetAddress InitAddr, JITSymbolFlags StubFlags) { auto Key = FreeStubs.back(); FreeStubs.pop_back(); *IndirectStubsInfos[Key.first].getPtr(Key.second) = reinterpret_cast(static_cast(InitAddr)); StubIndexes[StubName] = std::make_pair(Key, StubFlags); } std::mutex StubsMutex; std::vector IndirectStubsInfos; using StubKey = std::pair; std::vector FreeStubs; StringMap> StubIndexes; }; /// Create a local compile callback manager. /// /// The given target triple will determine the ABI, and the given /// ErrorHandlerAddress will be used by the resulting compile callback /// manager if a compile callback fails. Expected> createLocalCompileCallbackManager(const Triple &T, ExecutionSession &ES, JITTargetAddress ErrorHandlerAddress); /// Create a local indriect stubs manager builder. /// /// The given target triple will determine the ABI. std::function()> createLocalIndirectStubsManagerBuilder(const Triple &T); /// Build a function pointer of FunctionType with the given constant /// address. /// /// Usage example: Turn a trampoline address into a function pointer constant /// for use in a stub. Constant *createIRTypedAddress(FunctionType &FT, JITTargetAddress Addr); /// Create a function pointer with the given type, name, and initializer /// in the given Module. GlobalVariable *createImplPointer(PointerType &PT, Module &M, const Twine &Name, Constant *Initializer); /// Turn a function declaration into a stub function that makes an /// indirect call using the given function pointer. void makeStub(Function &F, Value &ImplPointer); /// Promotes private symbols to global hidden, and renames to prevent clashes /// with other promoted symbols. The same SymbolPromoter instance should be /// used for all symbols to be added to a single JITDylib. class SymbolLinkagePromoter { public: /// Promote symbols in the given module. Returns the set of global values /// that have been renamed/promoted. std::vector operator()(Module &M); private: unsigned NextId = 0; }; /// Clone a function declaration into a new module. /// /// This function can be used as the first step towards creating a callback /// stub (see makeStub), or moving a function body (see moveFunctionBody). /// /// If the VMap argument is non-null, a mapping will be added between F and /// the new declaration, and between each of F's arguments and the new /// declaration's arguments. This map can then be passed in to moveFunction to /// move the function body if required. Note: When moving functions between /// modules with these utilities, all decls should be cloned (and added to a /// single VMap) before any bodies are moved. This will ensure that references /// between functions all refer to the versions in the new module. Function *cloneFunctionDecl(Module &Dst, const Function &F, ValueToValueMapTy *VMap = nullptr); /// Move the body of function 'F' to a cloned function declaration in a /// different module (See related cloneFunctionDecl). /// /// If the target function declaration is not supplied via the NewF parameter /// then it will be looked up via the VMap. /// /// This will delete the body of function 'F' from its original parent module, /// but leave its declaration. void moveFunctionBody(Function &OrigF, ValueToValueMapTy &VMap, ValueMaterializer *Materializer = nullptr, Function *NewF = nullptr); /// Clone a global variable declaration into a new module. GlobalVariable *cloneGlobalVariableDecl(Module &Dst, const GlobalVariable &GV, ValueToValueMapTy *VMap = nullptr); /// Move global variable GV from its parent module to cloned global /// declaration in a different module. /// /// If the target global declaration is not supplied via the NewGV parameter /// then it will be looked up via the VMap. /// /// This will delete the initializer of GV from its original parent module, /// but leave its declaration. void moveGlobalVariableInitializer(GlobalVariable &OrigGV, ValueToValueMapTy &VMap, ValueMaterializer *Materializer = nullptr, GlobalVariable *NewGV = nullptr); /// Clone a global alias declaration into a new module. GlobalAlias *cloneGlobalAliasDecl(Module &Dst, const GlobalAlias &OrigA, ValueToValueMapTy &VMap); /// Clone module flags metadata into the destination module. void cloneModuleFlagsMetadata(Module &Dst, const Module &Src, ValueToValueMapTy &VMap); } // end namespace orc } // end namespace llvm #endif // LLVM_EXECUTIONENGINE_ORC_INDIRECTIONUTILS_H