1 //===------ LazyReexports.h -- Utilities for lazy reexports -----*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // Lazy re-exports are similar to normal re-exports, except that for callable
10 // symbols the definitions are replaced with trampolines that will look up and
11 // call through to the re-exported symbol at runtime. This can be used to
12 // enable lazy compilation.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #ifndef LLVM_EXECUTIONENGINE_ORC_LAZYREEXPORTS_H
17 #define LLVM_EXECUTIONENGINE_ORC_LAZYREEXPORTS_H
18 
19 #include "llvm/ExecutionEngine/Orc/Core.h"
20 #include "llvm/ExecutionEngine/Orc/IndirectionUtils.h"
21 #include "llvm/ExecutionEngine/Orc/Speculation.h"
22 
23 namespace llvm {
24 
25 class Triple;
26 
27 namespace orc {
28 
29 /// Manages a set of 'lazy call-through' trampolines. These are compiler
30 /// re-entry trampolines that are pre-bound to look up a given symbol in a given
31 /// JITDylib, then jump to that address. Since compilation of symbols is
32 /// triggered on first lookup, these call-through trampolines can be used to
33 /// implement lazy compilation.
34 ///
35 /// The easiest way to construct these call-throughs is using the lazyReexport
36 /// function.
37 class LazyCallThroughManager {
38 public:
39   /// Clients will want to take some action on first resolution, e.g. updating
40   /// a stub pointer. Instances of this class can be used to implement this.
41   class NotifyResolvedFunction {
42   public:
43     virtual ~NotifyResolvedFunction() {}
44 
45     /// Called the first time a lazy call through is executed and the target
46     /// symbol resolved.
47     virtual Error operator()(JITDylib &SourceJD,
48                              const SymbolStringPtr &SymbolName,
49                              JITTargetAddress ResolvedAddr) = 0;
50 
51   private:
52     virtual void anchor();
53   };
54 
55   template <typename NotifyResolvedImpl>
56   class NotifyResolvedFunctionImpl : public NotifyResolvedFunction {
57   public:
58     NotifyResolvedFunctionImpl(NotifyResolvedImpl NotifyResolved)
59         : NotifyResolved(std::move(NotifyResolved)) {}
60     Error operator()(JITDylib &SourceJD, const SymbolStringPtr &SymbolName,
61                      JITTargetAddress ResolvedAddr) {
62       return NotifyResolved(SourceJD, SymbolName, ResolvedAddr);
63     }
64 
65   private:
66     NotifyResolvedImpl NotifyResolved;
67   };
68 
69   /// Create a shared NotifyResolvedFunction from a given type that is
70   /// callable with the correct signature.
71   template <typename NotifyResolvedImpl>
72   static std::unique_ptr<NotifyResolvedFunction>
73   createNotifyResolvedFunction(NotifyResolvedImpl NotifyResolved) {
74     return std::make_unique<NotifyResolvedFunctionImpl<NotifyResolvedImpl>>(
75         std::move(NotifyResolved));
76   }
77 
78   // Return a free call-through trampoline and bind it to look up and call
79   // through to the given symbol.
80   Expected<JITTargetAddress> getCallThroughTrampoline(
81       JITDylib &SourceJD, SymbolStringPtr SymbolName,
82       std::shared_ptr<NotifyResolvedFunction> NotifyResolved);
83 
84 protected:
85   LazyCallThroughManager(ExecutionSession &ES,
86                          JITTargetAddress ErrorHandlerAddr,
87                          std::unique_ptr<TrampolinePool> TP);
88 
89   JITTargetAddress callThroughToSymbol(JITTargetAddress TrampolineAddr);
90 
91   void setTrampolinePool(std::unique_ptr<TrampolinePool> TP) {
92     this->TP = std::move(TP);
93   }
94 
95 private:
96   using ReexportsMap =
97       std::map<JITTargetAddress, std::pair<JITDylib *, SymbolStringPtr>>;
98 
99   using NotifiersMap =
100       std::map<JITTargetAddress, std::shared_ptr<NotifyResolvedFunction>>;
101 
102   std::mutex LCTMMutex;
103   ExecutionSession &ES;
104   JITTargetAddress ErrorHandlerAddr;
105   std::unique_ptr<TrampolinePool> TP;
106   ReexportsMap Reexports;
107   NotifiersMap Notifiers;
108 };
109 
110 /// A lazy call-through manager that builds trampolines in the current process.
111 class LocalLazyCallThroughManager : public LazyCallThroughManager {
112 private:
113   LocalLazyCallThroughManager(ExecutionSession &ES,
114                               JITTargetAddress ErrorHandlerAddr)
115       : LazyCallThroughManager(ES, ErrorHandlerAddr, nullptr) {}
116 
117   template <typename ORCABI> Error init() {
118     auto TP = LocalTrampolinePool<ORCABI>::Create(
119         [this](JITTargetAddress TrampolineAddr) {
120           return callThroughToSymbol(TrampolineAddr);
121         });
122 
123     if (!TP)
124       return TP.takeError();
125 
126     setTrampolinePool(std::move(*TP));
127     return Error::success();
128   }
129 
130 public:
131   /// Create a LocalLazyCallThroughManager using the given ABI. See
132   /// createLocalLazyCallThroughManager.
133   template <typename ORCABI>
134   static Expected<std::unique_ptr<LocalLazyCallThroughManager>>
135   Create(ExecutionSession &ES, JITTargetAddress ErrorHandlerAddr) {
136     auto LLCTM = std::unique_ptr<LocalLazyCallThroughManager>(
137         new LocalLazyCallThroughManager(ES, ErrorHandlerAddr));
138 
139     if (auto Err = LLCTM->init<ORCABI>())
140       return std::move(Err);
141 
142     return std::move(LLCTM);
143   }
144 };
145 
146 /// Create a LocalLazyCallThroughManager from the given triple and execution
147 /// session.
148 Expected<std::unique_ptr<LazyCallThroughManager>>
149 createLocalLazyCallThroughManager(const Triple &T, ExecutionSession &ES,
150                                   JITTargetAddress ErrorHandlerAddr);
151 
152 /// A materialization unit that builds lazy re-exports. These are callable
153 /// entry points that call through to the given symbols.
154 /// Unlike a 'true' re-export, the address of the lazy re-export will not
155 /// match the address of the re-exported symbol, but calling it will behave
156 /// the same as calling the re-exported symbol.
157 class LazyReexportsMaterializationUnit : public MaterializationUnit {
158 public:
159   LazyReexportsMaterializationUnit(LazyCallThroughManager &LCTManager,
160                                    IndirectStubsManager &ISManager,
161                                    JITDylib &SourceJD,
162                                    SymbolAliasMap CallableAliases,
163                                    ImplSymbolMap *SrcJDLoc, VModuleKey K);
164 
165   StringRef getName() const override;
166 
167 private:
168   void materialize(MaterializationResponsibility R) override;
169   void discard(const JITDylib &JD, const SymbolStringPtr &Name) override;
170   static SymbolFlagsMap extractFlags(const SymbolAliasMap &Aliases);
171 
172   LazyCallThroughManager &LCTManager;
173   IndirectStubsManager &ISManager;
174   JITDylib &SourceJD;
175   SymbolAliasMap CallableAliases;
176   std::shared_ptr<LazyCallThroughManager::NotifyResolvedFunction>
177       NotifyResolved;
178   ImplSymbolMap *AliaseeTable;
179 };
180 
181 /// Define lazy-reexports based on the given SymbolAliasMap. Each lazy re-export
182 /// is a callable symbol that will look up and dispatch to the given aliasee on
183 /// first call. All subsequent calls will go directly to the aliasee.
184 inline std::unique_ptr<LazyReexportsMaterializationUnit>
185 lazyReexports(LazyCallThroughManager &LCTManager,
186               IndirectStubsManager &ISManager, JITDylib &SourceJD,
187               SymbolAliasMap CallableAliases, ImplSymbolMap *SrcJDLoc = nullptr,
188               VModuleKey K = VModuleKey()) {
189   return std::make_unique<LazyReexportsMaterializationUnit>(
190       LCTManager, ISManager, SourceJD, std::move(CallableAliases), SrcJDLoc,
191       std::move(K));
192 }
193 
194 } // End namespace orc
195 } // End namespace llvm
196 
197 #endif // LLVM_EXECUTIONENGINE_ORC_LAZYREEXPORTS_H
198