--- /dev/null
+//===------ 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 <typename NotifyResolvedImpl>
+ 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 <typename NotifyResolvedImpl>
+ static std::unique_ptr<NotifyResolvedFunction>
+ createNotifyResolvedFunction(NotifyResolvedImpl NotifyResolved) {
+ return llvm::make_unique<NotifyResolvedFunctionImpl<NotifyResolvedImpl>>(
+ std::move(NotifyResolved));
+ };
+
+ // Return a free call-through trampoline and bind it to look up and call
+ // through to the given symbol.
+ Expected<JITTargetAddress> getCallThroughTrampoline(
+ JITDylib &SourceJD, SymbolStringPtr SymbolName,
+ std::shared_ptr<NotifyResolvedFunction> NotifyResolved);
+
+protected:
+ LazyCallThroughManager(ExecutionSession &ES,
+ JITTargetAddress ErrorHandlerAddr,
+ std::unique_ptr<TrampolinePool> TP);
+
+ JITTargetAddress callThroughToSymbol(JITTargetAddress TrampolineAddr);
+
+ void setTrampolinePool(std::unique_ptr<TrampolinePool> TP) {
+ this->TP = std::move(TP);
+ }
+
+private:
+ using ReexportsMap =
+ std::map<JITTargetAddress, std::pair<JITDylib *, SymbolStringPtr>>;
+
+ using NotifiersMap =
+ std::map<JITTargetAddress, std::shared_ptr<NotifyResolvedFunction>>;
+
+ std::mutex LCTMMutex;
+ ExecutionSession &ES;
+ JITTargetAddress ErrorHandlerAddr;
+ std::unique_ptr<TrampolinePool> 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 <typename ORCABI> Error init() {
+ auto TP = LocalTrampolinePool<ORCABI>::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 <typename ORCABI>
+ static Expected<std::unique_ptr<LocalLazyCallThroughManager>>
+ Create(ExecutionSession &ES, JITTargetAddress ErrorHandlerAddr) {
+ auto LLCTM = std::unique_ptr<LocalLazyCallThroughManager>(
+ new LocalLazyCallThroughManager(ES, ErrorHandlerAddr));
+
+ if (auto Err = LLCTM->init<ORCABI>())
+ return std::move(Err);
+
+ return std::move(LLCTM);
+ }
+};
+
+/// Create a LocalLazyCallThroughManager from the given triple and execution
+/// session.
+Expected<std::unique_ptr<LazyCallThroughManager>>
+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<LazyCallThroughManager::NotifyResolvedFunction>
+ 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<LazyReexportsMaterializationUnit>
+lazyReexports(LazyCallThroughManager &LCTManager,
+ IndirectStubsManager &ISManager, JITDylib &SourceJD,
+ SymbolAliasMap CallableAliases) {
+ return llvm::make_unique<LazyReexportsMaterializationUnit>(
+ LCTManager, ISManager, SourceJD, std::move(CallableAliases));
+}
+
+} // End namespace orc
+} // End namespace llvm
+
+#endif // LLVM_EXECUTIONENGINE_ORC_LAZYREEXPORTS_H
IndirectionUtils.cpp
IRCompileLayer.cpp
IRTransformLayer.cpp
+ LazyReexports.cpp
Legacy.cpp
Layer.cpp
LLJIT.cpp
--- /dev/null
+//===---------- 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<TrampolinePool> TP)
+ : ES(ES), ErrorHandlerAddr(ErrorHandlerAddr), TP(std::move(TP)) {}
+
+Expected<JITTargetAddress> LazyCallThroughManager::getCallThroughTrampoline(
+ JITDylib &SourceJD, SymbolStringPtr SymbolName,
+ std::shared_ptr<NotifyResolvedFunction> NotifyResolved) {
+ std::lock_guard<std::mutex> 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<std::mutex> 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<NotifyResolvedFunction> NotifyResolved = nullptr;
+ {
+ std::lock_guard<std::mutex> 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<std::unique_ptr<LazyCallThroughManager>>
+createLocalLazyCallThroughManager(const Triple &T, ExecutionSession &ES,
+ JITTargetAddress ErrorHandlerAddr) {
+ switch (T.getArch()) {
+ default:
+ return make_error<StringError>(
+ std::string("No callback manager available for ") + T.str(),
+ inconvertibleErrorCode());
+
+ case Triple::aarch64:
+ return LocalLazyCallThroughManager::Create<OrcAArch64>(ES,
+ ErrorHandlerAddr);
+
+ case Triple::x86:
+ return LocalLazyCallThroughManager::Create<OrcI386>(ES, ErrorHandlerAddr);
+
+ case Triple::mips:
+ return LocalLazyCallThroughManager::Create<OrcMips32Be>(ES,
+ ErrorHandlerAddr);
+
+ case Triple::mipsel:
+ return LocalLazyCallThroughManager::Create<OrcMips32Le>(ES,
+ ErrorHandlerAddr);
+
+ case Triple::mips64:
+ case Triple::mips64el:
+ return LocalLazyCallThroughManager::Create<OrcMips64>(ES, ErrorHandlerAddr);
+
+ case Triple::x86_64:
+ if (T.getOS() == Triple::OSType::Win32)
+ return LocalLazyCallThroughManager::Create<OrcX86_64_Win32>(
+ ES, ErrorHandlerAddr);
+ else
+ return LocalLazyCallThroughManager::Create<OrcX86_64_SysV>(
+ 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.
CoreAPIsTest.cpp
IndirectionUtilsTest.cpp
GlobalMappingLayerTest.cpp
+ LazyCallThroughAndReexportsTest.cpp
LazyEmittingLayerTest.cpp
LegacyAPIInteropTest.cpp
ObjectTransformLayerTest.cpp
namespace {
-class SimpleMaterializationUnit : public MaterializationUnit {
-public:
- using MaterializeFunction =
- std::function<void(MaterializationResponsibility)>;
- using DiscardFunction =
- std::function<void(const JITDylib &, SymbolStringPtr)>;
- using DestructorFunction = std::function<void()>;
-
- 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;
--- /dev/null
+#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<SimpleMaterializationUnit>(
+ SymbolFlagsMap({{DummyTarget, JITSymbolFlags::Exported}}),
+ [&](MaterializationResponsibility R) {
+ DummyTargetMaterialized = true;
+ R.resolve(
+ {{DummyTarget,
+ JITEvaluatedSymbol(static_cast<JITTargetAddress>(
+ reinterpret_cast<uintptr_t>(&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<int (*)()>(
+ static_cast<uintptr_t>(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";
+}
static bool NativeTargetInitialized;
};
+class SimpleMaterializationUnit : public orc::MaterializationUnit {
+public:
+ using MaterializeFunction =
+ std::function<void(orc::MaterializationResponsibility)>;
+ using DiscardFunction =
+ std::function<void(const orc::JITDylib &, orc::SymbolStringPtr)>;
+ using DestructorFunction = std::function<void()>;
+
+ 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: