From: Lang Hames Date: Wed, 26 Sep 2018 04:18:30 +0000 (+0000) Subject: [ORC] Add a "lazy call-through" utility based on the same underlying trampoline X-Git-Tag: llvmorg-8.0.0-rc1~7924 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c1275e72cb47b7a9ef5a6a2ffe1961f3e0025528;p=platform%2Fupstream%2Fllvm.git [ORC] Add a "lazy call-through" utility based on the same underlying trampoline implementation as lazy compile callbacks, and a "lazy re-exports" utility that builds lazy call-throughs. Lazy call-throughs are similar to lazy compile callbacks (and are based on the same underlying state saving/restoring trampolines) but resolve their targets by performing a standard ORC lookup rather than invoking a user supplied compiler callback. This allows them to inherit the thread-safety of ORC lookups while blocking only the calling thread (whereas compile callbacks also block one compile thread). Lazy re-exports provide a simple way of building lazy call-throughs. Unlike a regular re-export, a lazy re-export generates a new address (a stub entry point) that will act like the re-exported symbol when called. The first call via a lazy re-export will trigger compilation of the re-exported symbol before calling through to it. llvm-svn: 343061 --- diff --git a/llvm/include/llvm/ExecutionEngine/Orc/LazyReexports.h b/llvm/include/llvm/ExecutionEngine/Orc/LazyReexports.h new file mode 100644 index 0000000..dacc2eb --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/LazyReexports.h @@ -0,0 +1,191 @@ +//===------ LazyReexports.h -- Utilities for lazy reexports -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Lazy re-exports are similar to normal re-exports, except that for callable +// symbols the definitions are replaced with trampolines that will look up and +// call through to the re-exported symbol at runtime. This can be used to +// enable lazy compilation. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_LAZYREEXPORTS_H +#define LLVM_EXECUTIONENGINE_ORC_LAZYREEXPORTS_H + +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/IndirectionUtils.h" + +namespace llvm { + +class Triple; + +namespace orc { + +/// Manages a set of 'lazy call-through' trampolines. These are compiler +/// re-entry trampolines that are pre-bound to look up a given symbol in a given +/// JITDylib, then jump to that address. Since compilation of symbols is +/// triggered on first lookup, these call-through trampolines can be used to +/// implement lazy compilation. +/// +/// The easiest way to construct these call-throughs is using the lazyReexport +/// function. +class LazyCallThroughManager { +public: + /// Clients will want to take some action on first resolution, e.g. updating + /// a stub pointer. Instances of this class can be used to implement this. + class NotifyResolvedFunction { + public: + virtual ~NotifyResolvedFunction() {} + + /// Called the first time a lazy call through is executed and the target + /// symbol resolved. + virtual Error operator()(JITDylib &SourceJD, + const SymbolStringPtr &SymbolName, + JITTargetAddress ResolvedAddr) = 0; + + private: + virtual void anchor(); + }; + + template + class NotifyResolvedFunctionImpl : public NotifyResolvedFunction { + public: + NotifyResolvedFunctionImpl(NotifyResolvedImpl NotifyResolved) + : NotifyResolved(std::move(NotifyResolved)) {} + Error operator()(JITDylib &SourceJD, const SymbolStringPtr &SymbolName, + JITTargetAddress ResolvedAddr) { + return NotifyResolved(SourceJD, SymbolName, ResolvedAddr); + } + + private: + NotifyResolvedImpl NotifyResolved; + }; + + /// Create a shared NotifyResolvedFunction from a given type that is + /// callable with the correct signature. + template + static std::unique_ptr + createNotifyResolvedFunction(NotifyResolvedImpl NotifyResolved) { + return llvm::make_unique>( + std::move(NotifyResolved)); + }; + + // Return a free call-through trampoline and bind it to look up and call + // through to the given symbol. + Expected getCallThroughTrampoline( + JITDylib &SourceJD, SymbolStringPtr SymbolName, + std::shared_ptr NotifyResolved); + +protected: + LazyCallThroughManager(ExecutionSession &ES, + JITTargetAddress ErrorHandlerAddr, + std::unique_ptr TP); + + JITTargetAddress callThroughToSymbol(JITTargetAddress TrampolineAddr); + + void setTrampolinePool(std::unique_ptr TP) { + this->TP = std::move(TP); + } + +private: + using ReexportsMap = + std::map>; + + using NotifiersMap = + std::map>; + + std::mutex LCTMMutex; + ExecutionSession &ES; + JITTargetAddress ErrorHandlerAddr; + std::unique_ptr TP; + ReexportsMap Reexports; + NotifiersMap Notifiers; +}; + +/// A lazy call-through manager that builds trampolines in the current process. +class LocalLazyCallThroughManager : public LazyCallThroughManager { +private: + LocalLazyCallThroughManager(ExecutionSession &ES, + JITTargetAddress ErrorHandlerAddr) + : LazyCallThroughManager(ES, ErrorHandlerAddr, nullptr) {} + + template Error init() { + auto TP = LocalTrampolinePool::Create( + [this](JITTargetAddress TrampolineAddr) { + return callThroughToSymbol(TrampolineAddr); + }); + + if (!TP) + return TP.takeError(); + + setTrampolinePool(std::move(*TP)); + return Error::success(); + } + +public: + /// Create a LocalLazyCallThroughManager using the given ABI. See + /// createLocalLazyCallThroughManager. + template + static Expected> + Create(ExecutionSession &ES, JITTargetAddress ErrorHandlerAddr) { + auto LLCTM = std::unique_ptr( + new LocalLazyCallThroughManager(ES, ErrorHandlerAddr)); + + if (auto Err = LLCTM->init()) + return std::move(Err); + + return std::move(LLCTM); + } +}; + +/// Create a LocalLazyCallThroughManager from the given triple and execution +/// session. +Expected> +createLocalLazyCallThroughManager(const Triple &T, ExecutionSession &ES, + JITTargetAddress ErrorHandlerAddr); + +/// A materialization unit that builds lazy re-exports. These are callable +/// entry points that call through to the given symbols. +/// Unlike a 'true' re-export, the address of the lazy re-export will not +/// match the address of the re-exported symbol, but calling it will behave +/// the same as calling the re-exported symbol. +class LazyReexportsMaterializationUnit : public MaterializationUnit { +public: + LazyReexportsMaterializationUnit(LazyCallThroughManager &LCTManager, + IndirectStubsManager &ISManager, + JITDylib &SourceJD, + SymbolAliasMap CallableAliases); + +private: + void materialize(MaterializationResponsibility R) override; + void discard(const JITDylib &JD, SymbolStringPtr Name) override; + static SymbolFlagsMap extractFlags(const SymbolAliasMap &Aliases); + + LazyCallThroughManager &LCTManager; + IndirectStubsManager &ISManager; + JITDylib &SourceJD; + SymbolAliasMap CallableAliases; + std::shared_ptr + NotifyResolved; +}; + +/// Define lazy-reexports based on the given SymbolAliasMap. Each lazy re-export +/// is a callable symbol that will look up and dispatch to the given aliasee on +/// first call. All subsequent calls will go directly to the aliasee. +inline std::unique_ptr +lazyReexports(LazyCallThroughManager &LCTManager, + IndirectStubsManager &ISManager, JITDylib &SourceJD, + SymbolAliasMap CallableAliases) { + return llvm::make_unique( + LCTManager, ISManager, SourceJD, std::move(CallableAliases)); +} + +} // End namespace orc +} // End namespace llvm + +#endif // LLVM_EXECUTIONENGINE_ORC_LAZYREEXPORTS_H diff --git a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt index 59c9ee7..680ede2 100644 --- a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt @@ -5,6 +5,7 @@ add_llvm_library(LLVMOrcJIT IndirectionUtils.cpp IRCompileLayer.cpp IRTransformLayer.cpp + LazyReexports.cpp Legacy.cpp Layer.cpp LLJIT.cpp diff --git a/llvm/lib/ExecutionEngine/Orc/LazyReexports.cpp b/llvm/lib/ExecutionEngine/Orc/LazyReexports.cpp new file mode 100644 index 0000000..c327005 --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/LazyReexports.cpp @@ -0,0 +1,204 @@ +//===---------- LazyReexports.cpp - Utilities for lazy reexports ----------===// +// +// 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/LazyReexports.h" + +#include "llvm/ADT/Triple.h" +#include "llvm/ExecutionEngine/Orc/OrcABISupport.h" + +#define DEBUG_TYPE "orc" + +namespace llvm { +namespace orc { + +void LazyCallThroughManager::NotifyResolvedFunction::anchor() {} + +LazyCallThroughManager::LazyCallThroughManager( + ExecutionSession &ES, JITTargetAddress ErrorHandlerAddr, + std::unique_ptr TP) + : ES(ES), ErrorHandlerAddr(ErrorHandlerAddr), TP(std::move(TP)) {} + +Expected LazyCallThroughManager::getCallThroughTrampoline( + JITDylib &SourceJD, SymbolStringPtr SymbolName, + std::shared_ptr NotifyResolved) { + std::lock_guard Lock(LCTMMutex); + auto Trampoline = TP->getTrampoline(); + + if (!Trampoline) + return Trampoline.takeError(); + + Reexports[*Trampoline] = std::make_pair(&SourceJD, std::move(SymbolName)); + Notifiers[*Trampoline] = std::move(NotifyResolved); + return *Trampoline; +} + +JITTargetAddress +LazyCallThroughManager::callThroughToSymbol(JITTargetAddress TrampolineAddr) { + JITDylib *SourceJD = nullptr; + SymbolStringPtr SymbolName; + + { + std::lock_guard Lock(LCTMMutex); + auto I = Reexports.find(TrampolineAddr); + if (I == Reexports.end()) + return ErrorHandlerAddr; + SourceJD = I->second.first; + SymbolName = I->second.second; + } + + auto LookupResult = + ES.lookup({SourceJD}, {SymbolName}, NoDependenciesToRegister); + + if (!LookupResult) { + ES.reportError(LookupResult.takeError()); + return ErrorHandlerAddr; + } + + assert(LookupResult->size() == 1 && "Unexpected number of results"); + assert(LookupResult->count(SymbolName) && "Unexpected result"); + + auto ResolvedAddr = LookupResult->begin()->second.getAddress(); + + std::shared_ptr NotifyResolved = nullptr; + { + std::lock_guard Lock(LCTMMutex); + auto I = Notifiers.find(TrampolineAddr); + if (I != Notifiers.end()) { + NotifyResolved = I->second; + Notifiers.erase(I); + } + } + + if (NotifyResolved) { + if (auto Err = (*NotifyResolved)(*SourceJD, SymbolName, ResolvedAddr)) { + ES.reportError(std::move(Err)); + return ErrorHandlerAddr; + } + } + + return ResolvedAddr; +} + +Expected> +createLocalLazyCallThroughManager(const Triple &T, ExecutionSession &ES, + JITTargetAddress ErrorHandlerAddr) { + switch (T.getArch()) { + default: + return make_error( + std::string("No callback manager available for ") + T.str(), + inconvertibleErrorCode()); + + case Triple::aarch64: + return LocalLazyCallThroughManager::Create(ES, + ErrorHandlerAddr); + + case Triple::x86: + return LocalLazyCallThroughManager::Create(ES, ErrorHandlerAddr); + + case Triple::mips: + return LocalLazyCallThroughManager::Create(ES, + ErrorHandlerAddr); + + case Triple::mipsel: + return LocalLazyCallThroughManager::Create(ES, + ErrorHandlerAddr); + + case Triple::mips64: + case Triple::mips64el: + return LocalLazyCallThroughManager::Create(ES, ErrorHandlerAddr); + + case Triple::x86_64: + if (T.getOS() == Triple::OSType::Win32) + return LocalLazyCallThroughManager::Create( + ES, ErrorHandlerAddr); + else + return LocalLazyCallThroughManager::Create( + ES, ErrorHandlerAddr); + } +} + +LazyReexportsMaterializationUnit::LazyReexportsMaterializationUnit( + LazyCallThroughManager &LCTManager, IndirectStubsManager &ISManager, + JITDylib &SourceJD, SymbolAliasMap CallableAliases) + : MaterializationUnit(extractFlags(CallableAliases)), + LCTManager(LCTManager), ISManager(ISManager), SourceJD(SourceJD), + CallableAliases(std::move(CallableAliases)), + NotifyResolved(LazyCallThroughManager::createNotifyResolvedFunction( + [&ISManager](JITDylib &JD, const SymbolStringPtr &SymbolName, + JITTargetAddress ResolvedAddr) { + return ISManager.updatePointer(*SymbolName, ResolvedAddr); + })) {} + +void LazyReexportsMaterializationUnit::materialize( + MaterializationResponsibility R) { + auto RequestedSymbols = R.getRequestedSymbols(); + + SymbolAliasMap RequestedAliases; + for (auto &RequestedSymbol : RequestedSymbols) { + auto I = CallableAliases.find(RequestedSymbol); + assert(I != CallableAliases.end() && "Symbol not found in alias map?"); + RequestedAliases[I->first] = std::move(I->second); + CallableAliases.erase(I); + } + + if (!CallableAliases.empty()) + R.replace(lazyReexports(LCTManager, ISManager, SourceJD, + std::move(CallableAliases))); + + IndirectStubsManager::StubInitsMap StubInits; + for (auto &Alias : RequestedAliases) { + + auto CallThroughTrampoline = LCTManager.getCallThroughTrampoline( + SourceJD, Alias.second.Aliasee, NotifyResolved); + + if (!CallThroughTrampoline) { + SourceJD.getExecutionSession().reportError( + CallThroughTrampoline.takeError()); + R.failMaterialization(); + return; + } + + StubInits[*Alias.first] = + std::make_pair(*CallThroughTrampoline, Alias.second.AliasFlags); + } + + if (auto Err = ISManager.createStubs(StubInits)) { + SourceJD.getExecutionSession().reportError(std::move(Err)); + R.failMaterialization(); + return; + } + + SymbolMap Stubs; + for (auto &Alias : RequestedAliases) + Stubs[Alias.first] = ISManager.findStub(*Alias.first, false); + + R.resolve(Stubs); + R.emit(); +} + +void LazyReexportsMaterializationUnit::discard(const JITDylib &JD, + SymbolStringPtr Name) { + assert(CallableAliases.count(Name) && + "Symbol not covered by this MaterializationUnit"); + CallableAliases.erase(Name); +} + +SymbolFlagsMap +LazyReexportsMaterializationUnit::extractFlags(const SymbolAliasMap &Aliases) { + SymbolFlagsMap SymbolFlags; + for (auto &KV : Aliases) { + assert(KV.second.AliasFlags.isCallable() && + "Lazy re-exports must be callable symbols"); + SymbolFlags[KV.first] = KV.second.AliasFlags; + } + return SymbolFlags; +} + +} // End namespace orc. +} // End namespace llvm. diff --git a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt index f0fd589..c18c936 100644 --- a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt @@ -14,6 +14,7 @@ add_llvm_unittest(OrcJITTests CoreAPIsTest.cpp IndirectionUtilsTest.cpp GlobalMappingLayerTest.cpp + LazyCallThroughAndReexportsTest.cpp LazyEmittingLayerTest.cpp LegacyAPIInteropTest.cpp ObjectTransformLayerTest.cpp diff --git a/llvm/unittests/ExecutionEngine/Orc/CoreAPIsTest.cpp b/llvm/unittests/ExecutionEngine/Orc/CoreAPIsTest.cpp index b3cd4b5..d0e4eff 100644 --- a/llvm/unittests/ExecutionEngine/Orc/CoreAPIsTest.cpp +++ b/llvm/unittests/ExecutionEngine/Orc/CoreAPIsTest.cpp @@ -22,44 +22,6 @@ class CoreAPIsStandardTest : public CoreAPIsBasedStandardTest {}; namespace { -class SimpleMaterializationUnit : public MaterializationUnit { -public: - using MaterializeFunction = - std::function; - using DiscardFunction = - std::function; - using DestructorFunction = std::function; - - SimpleMaterializationUnit( - SymbolFlagsMap SymbolFlags, MaterializeFunction Materialize, - DiscardFunction Discard = DiscardFunction(), - DestructorFunction Destructor = DestructorFunction()) - : MaterializationUnit(std::move(SymbolFlags)), - Materialize(std::move(Materialize)), Discard(std::move(Discard)), - Destructor(std::move(Destructor)) {} - - ~SimpleMaterializationUnit() override { - if (Destructor) - Destructor(); - } - - void materialize(MaterializationResponsibility R) override { - Materialize(std::move(R)); - } - - void discard(const JITDylib &JD, SymbolStringPtr Name) override { - if (Discard) - Discard(JD, std::move(Name)); - else - llvm_unreachable("Discard not supported"); - } - -private: - MaterializeFunction Materialize; - DiscardFunction Discard; - DestructorFunction Destructor; -}; - TEST_F(CoreAPIsStandardTest, BasicSuccessfulLookup) { bool OnResolutionRun = false; bool OnReadyRun = false; diff --git a/llvm/unittests/ExecutionEngine/Orc/LazyCallThroughAndReexportsTest.cpp b/llvm/unittests/ExecutionEngine/Orc/LazyCallThroughAndReexportsTest.cpp new file mode 100644 index 0000000..7caaa76 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/Orc/LazyCallThroughAndReexportsTest.cpp @@ -0,0 +1,75 @@ +#include "OrcTestCommon.h" +#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" +#include "llvm/ExecutionEngine/Orc/LazyReexports.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::orc; + +class LazyReexportsTest : public CoreAPIsBasedStandardTest {}; + +static int dummyTarget() { return 42; } + +TEST_F(LazyReexportsTest, BasicLocalCallThroughManagerOperation) { + // Create a callthrough manager for the host (if possible) and verify that + // a call to the lazy call-through: + // (1) Materializes the MU. This verifies that the symbol was looked up, and + // that we didn't arrive at the target via some other path + // (2) Returns the expected value (which we take as proof that the call + // reached the target). + + auto JTMB = JITTargetMachineBuilder::detectHost(); + + // Bail out if we can not detect the host. + if (!JTMB) { + consumeError(JTMB.takeError()); + return; + } + + // Bail out if we can not build a local call-through manager. + auto LCTM = createLocalLazyCallThroughManager(JTMB->getTargetTriple(), ES, 0); + if (!LCTM) { + consumeError(LCTM.takeError()); + return; + } + + auto DummyTarget = ES.getSymbolStringPool().intern("DummyTarget"); + + bool DummyTargetMaterialized = false; + + cantFail(JD.define(llvm::make_unique( + SymbolFlagsMap({{DummyTarget, JITSymbolFlags::Exported}}), + [&](MaterializationResponsibility R) { + DummyTargetMaterialized = true; + R.resolve( + {{DummyTarget, + JITEvaluatedSymbol(static_cast( + reinterpret_cast(&dummyTarget)), + JITSymbolFlags::Exported)}}); + R.emit(); + }))); + + unsigned NotifyResolvedCount = 0; + auto NotifyResolved = LazyCallThroughManager::createNotifyResolvedFunction( + [&](JITDylib &JD, const SymbolStringPtr &SymbolName, + JITTargetAddress ResolvedAddr) { + ++NotifyResolvedCount; + return Error::success(); + }); + + auto CallThroughTrampoline = cantFail((*LCTM)->getCallThroughTrampoline( + JD, DummyTarget, std::move(NotifyResolved))); + + auto CTTPtr = reinterpret_cast( + static_cast(CallThroughTrampoline)); + + // Call twice to verify nothing unexpected happens on redundant calls. + auto Result = CTTPtr(); + (void)CTTPtr(); + + EXPECT_TRUE(DummyTargetMaterialized) + << "CallThrough did not materialize target"; + EXPECT_EQ(NotifyResolvedCount, 1U) + << "CallThrough should have generated exactly one 'NotifyResolved' call"; + EXPECT_EQ(Result, 42) << "Failed to call through to target"; +} diff --git a/llvm/unittests/ExecutionEngine/Orc/OrcTestCommon.h b/llvm/unittests/ExecutionEngine/Orc/OrcTestCommon.h index acc0e5b..23ca74c 100644 --- a/llvm/unittests/ExecutionEngine/Orc/OrcTestCommon.h +++ b/llvm/unittests/ExecutionEngine/Orc/OrcTestCommon.h @@ -85,6 +85,44 @@ private: static bool NativeTargetInitialized; }; +class SimpleMaterializationUnit : public orc::MaterializationUnit { +public: + using MaterializeFunction = + std::function; + using DiscardFunction = + std::function; + using DestructorFunction = std::function; + + SimpleMaterializationUnit( + orc::SymbolFlagsMap SymbolFlags, MaterializeFunction Materialize, + DiscardFunction Discard = DiscardFunction(), + DestructorFunction Destructor = DestructorFunction()) + : MaterializationUnit(std::move(SymbolFlags)), + Materialize(std::move(Materialize)), Discard(std::move(Discard)), + Destructor(std::move(Destructor)) {} + + ~SimpleMaterializationUnit() override { + if (Destructor) + Destructor(); + } + + void materialize(orc::MaterializationResponsibility R) override { + Materialize(std::move(R)); + } + + void discard(const orc::JITDylib &JD, orc::SymbolStringPtr Name) override { + if (Discard) + Discard(JD, std::move(Name)); + else + llvm_unreachable("Discard not supported"); + } + +private: + MaterializeFunction Materialize; + DiscardFunction Discard; + DestructorFunction Destructor; +}; + // Base class for Orc tests that will execute code. class OrcExecutionTest { public: