xref: /llvm-project/llvm/include/llvm/ExecutionEngine/Orc/LazyReexports.h (revision 2d10b7b750f97b42055d5b9b08a88c18ff811cd2)
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/ADT/STLExtras.h"
20 #include "llvm/ExecutionEngine/Orc/Core.h"
21 #include "llvm/ExecutionEngine/Orc/IndirectionUtils.h"
22 #include "llvm/ExecutionEngine/Orc/RedirectionManager.h"
23 #include "llvm/ExecutionEngine/Orc/Speculation.h"
24 
25 namespace llvm {
26 
27 class Triple;
28 
29 namespace orc {
30 
31 /// Manages a set of 'lazy call-through' trampolines. These are compiler
32 /// re-entry trampolines that are pre-bound to look up a given symbol in a given
33 /// JITDylib, then jump to that address. Since compilation of symbols is
34 /// triggered on first lookup, these call-through trampolines can be used to
35 /// implement lazy compilation.
36 ///
37 /// The easiest way to construct these call-throughs is using the lazyReexport
38 /// function.
39 class LazyCallThroughManager {
40 public:
41   using NotifyResolvedFunction =
42       unique_function<Error(ExecutorAddr ResolvedAddr)>;
43 
44   LazyCallThroughManager(ExecutionSession &ES, ExecutorAddr ErrorHandlerAddr,
45                          TrampolinePool *TP);
46 
47   // Return a free call-through trampoline and bind it to look up and call
48   // through to the given symbol.
49   Expected<ExecutorAddr>
50   getCallThroughTrampoline(JITDylib &SourceJD, SymbolStringPtr SymbolName,
51                            NotifyResolvedFunction NotifyResolved);
52 
53   void resolveTrampolineLandingAddress(
54       ExecutorAddr TrampolineAddr,
55       TrampolinePool::NotifyLandingResolvedFunction NotifyLandingResolved);
56 
57   virtual ~LazyCallThroughManager() = default;
58 
59 protected:
60   using NotifyLandingResolvedFunction =
61       TrampolinePool::NotifyLandingResolvedFunction;
62 
63   struct ReexportsEntry {
64     JITDylib *SourceJD;
65     SymbolStringPtr SymbolName;
66   };
67 
68   ExecutorAddr reportCallThroughError(Error Err);
69   Expected<ReexportsEntry> findReexport(ExecutorAddr TrampolineAddr);
70   Error notifyResolved(ExecutorAddr TrampolineAddr, ExecutorAddr ResolvedAddr);
71   void setTrampolinePool(TrampolinePool &TP) { this->TP = &TP; }
72 
73 private:
74   using ReexportsMap = std::map<ExecutorAddr, ReexportsEntry>;
75 
76   using NotifiersMap = std::map<ExecutorAddr, NotifyResolvedFunction>;
77 
78   std::mutex LCTMMutex;
79   ExecutionSession &ES;
80   ExecutorAddr ErrorHandlerAddr;
81   TrampolinePool *TP = nullptr;
82   ReexportsMap Reexports;
83   NotifiersMap Notifiers;
84 };
85 
86 /// A lazy call-through manager that builds trampolines in the current process.
87 class LocalLazyCallThroughManager : public LazyCallThroughManager {
88 private:
89   using NotifyTargetResolved = unique_function<void(ExecutorAddr)>;
90 
91   LocalLazyCallThroughManager(ExecutionSession &ES,
92                               ExecutorAddr ErrorHandlerAddr)
93       : LazyCallThroughManager(ES, ErrorHandlerAddr, nullptr) {}
94 
95   template <typename ORCABI> Error init() {
96     auto TP = LocalTrampolinePool<ORCABI>::Create(
97         [this](ExecutorAddr TrampolineAddr,
98                TrampolinePool::NotifyLandingResolvedFunction
99                    NotifyLandingResolved) {
100           resolveTrampolineLandingAddress(TrampolineAddr,
101                                           std::move(NotifyLandingResolved));
102         });
103 
104     if (!TP)
105       return TP.takeError();
106 
107     this->TP = std::move(*TP);
108     setTrampolinePool(*this->TP);
109     return Error::success();
110   }
111 
112   std::unique_ptr<TrampolinePool> TP;
113 
114 public:
115   /// Create a LocalLazyCallThroughManager using the given ABI. See
116   /// createLocalLazyCallThroughManager.
117   template <typename ORCABI>
118   static Expected<std::unique_ptr<LocalLazyCallThroughManager>>
119   Create(ExecutionSession &ES, ExecutorAddr ErrorHandlerAddr) {
120     auto LLCTM = std::unique_ptr<LocalLazyCallThroughManager>(
121         new LocalLazyCallThroughManager(ES, ErrorHandlerAddr));
122 
123     if (auto Err = LLCTM->init<ORCABI>())
124       return std::move(Err);
125 
126     return std::move(LLCTM);
127   }
128 };
129 
130 /// Create a LocalLazyCallThroughManager from the given triple and execution
131 /// session.
132 Expected<std::unique_ptr<LazyCallThroughManager>>
133 createLocalLazyCallThroughManager(const Triple &T, ExecutionSession &ES,
134                                   ExecutorAddr ErrorHandlerAddr);
135 
136 /// A materialization unit that builds lazy re-exports. These are callable
137 /// entry points that call through to the given symbols.
138 /// Unlike a 'true' re-export, the address of the lazy re-export will not
139 /// match the address of the re-exported symbol, but calling it will behave
140 /// the same as calling the re-exported symbol.
141 class LazyReexportsMaterializationUnit : public MaterializationUnit {
142 public:
143   LazyReexportsMaterializationUnit(LazyCallThroughManager &LCTManager,
144                                    RedirectableSymbolManager &RSManager,
145                                    JITDylib &SourceJD,
146                                    SymbolAliasMap CallableAliases,
147                                    ImplSymbolMap *SrcJDLoc);
148 
149   StringRef getName() const override;
150 
151 private:
152   void materialize(std::unique_ptr<MaterializationResponsibility> R) override;
153   void discard(const JITDylib &JD, const SymbolStringPtr &Name) override;
154   static MaterializationUnit::Interface
155   extractFlags(const SymbolAliasMap &Aliases);
156 
157   LazyCallThroughManager &LCTManager;
158   RedirectableSymbolManager &RSManager;
159   JITDylib &SourceJD;
160   SymbolAliasMap CallableAliases;
161   ImplSymbolMap *AliaseeTable;
162 };
163 
164 /// Define lazy-reexports based on the given SymbolAliasMap. Each lazy re-export
165 /// is a callable symbol that will look up and dispatch to the given aliasee on
166 /// first call. All subsequent calls will go directly to the aliasee.
167 inline std::unique_ptr<LazyReexportsMaterializationUnit>
168 lazyReexports(LazyCallThroughManager &LCTManager,
169               RedirectableSymbolManager &RSManager, JITDylib &SourceJD,
170               SymbolAliasMap CallableAliases,
171               ImplSymbolMap *SrcJDLoc = nullptr) {
172   return std::make_unique<LazyReexportsMaterializationUnit>(
173       LCTManager, RSManager, SourceJD, std::move(CallableAliases), SrcJDLoc);
174 }
175 
176 class LazyReexportsManager : public ResourceManager {
177 
178   friend std::unique_ptr<MaterializationUnit>
179   lazyReexports(LazyReexportsManager &, SymbolAliasMap);
180 
181 public:
182   struct CallThroughInfo {
183     JITDylibSP JD;
184     SymbolStringPtr Name;
185     SymbolStringPtr BodyName;
186   };
187 
188   class Listener {
189   public:
190     using CallThroughInfo = LazyReexportsManager::CallThroughInfo;
191 
192     virtual ~Listener();
193 
194     /// Called under the session lock when new lazy reexports are created.
195     virtual void onLazyReexportsCreated(JITDylib &JD, ResourceKey K,
196                                         const SymbolAliasMap &Reexports) = 0;
197 
198     /// Called under the session lock when lazy reexports have their ownership
199     /// transferred to a new ResourceKey.
200     virtual void onLazyReexportsTransfered(JITDylib &JD, ResourceKey DstK,
201                                            ResourceKey SrcK) = 0;
202 
203     /// Called under the session lock when lazy reexports are removed.
204     virtual Error onLazyReexportsRemoved(JITDylib &JD, ResourceKey K) = 0;
205 
206     /// Called outside the session lock when a lazy reexport is called.
207     /// NOTE: Since this is called outside the session lock there is a chance
208     ///       that the reexport referred to has already been removed. Listeners
209     ///       must be prepared to handle requests for stale reexports.
210     virtual void onLazyReexportCalled(const CallThroughInfo &CTI) = 0;
211   };
212 
213   using OnTrampolinesReadyFn = unique_function<void(
214       Expected<std::vector<ExecutorSymbolDef>> EntryAddrs)>;
215   using EmitTrampolinesFn =
216       unique_function<void(ResourceTrackerSP RT, size_t NumTrampolines,
217                            OnTrampolinesReadyFn OnTrampolinesReady)>;
218 
219   /// Create a LazyReexportsManager that uses the ORC runtime for reentry.
220   /// This will work both in-process and out-of-process.
221   static Expected<std::unique_ptr<LazyReexportsManager>>
222   Create(EmitTrampolinesFn EmitTrampolines, RedirectableSymbolManager &RSMgr,
223          JITDylib &PlatformJD, Listener *L = nullptr);
224 
225   LazyReexportsManager(LazyReexportsManager &&) = delete;
226   LazyReexportsManager &operator=(LazyReexportsManager &&) = delete;
227 
228   Error handleRemoveResources(JITDylib &JD, ResourceKey K) override;
229   void handleTransferResources(JITDylib &JD, ResourceKey DstK,
230                                ResourceKey SrcK) override;
231 
232 private:
233   class MU;
234   class Plugin;
235 
236   using ResolveSendResultFn =
237       unique_function<void(Expected<ExecutorSymbolDef>)>;
238 
239   LazyReexportsManager(EmitTrampolinesFn EmitTrampolines,
240                        RedirectableSymbolManager &RSMgr, JITDylib &PlatformJD,
241                        Listener *L, Error &Err);
242 
243   std::unique_ptr<MaterializationUnit>
244   createLazyReexports(SymbolAliasMap Reexports);
245 
246   void emitReentryTrampolines(std::unique_ptr<MaterializationResponsibility> MR,
247                               SymbolAliasMap Reexports);
248   void emitRedirectableSymbols(
249       std::unique_ptr<MaterializationResponsibility> MR,
250       SymbolAliasMap Reexports,
251       Expected<std::vector<ExecutorSymbolDef>> ReentryPoints);
252   void resolve(ResolveSendResultFn SendResult, ExecutorAddr ReentryStubAddr);
253 
254   ExecutionSession &ES;
255   EmitTrampolinesFn EmitTrampolines;
256   RedirectableSymbolManager &RSMgr;
257   Listener *L;
258 
259   DenseMap<ResourceKey, std::vector<ExecutorAddr>> KeyToReentryAddrs;
260   DenseMap<ExecutorAddr, CallThroughInfo> CallThroughs;
261 };
262 
263 /// Define lazy-reexports based on the given SymbolAliasMap. Each lazy re-export
264 /// is a callable symbol that will look up and dispatch to the given aliasee on
265 /// first call. All subsequent calls will go directly to the aliasee.
266 inline std::unique_ptr<MaterializationUnit>
267 lazyReexports(LazyReexportsManager &LRM, SymbolAliasMap Reexports) {
268   return LRM.createLazyReexports(std::move(Reexports));
269 }
270 
271 class SimpleLazyReexportsSpeculator : public LazyReexportsManager::Listener {
272 public:
273   using RecordExecutionFunction =
274       unique_function<void(const CallThroughInfo &CTI)>;
275 
276   static std::shared_ptr<SimpleLazyReexportsSpeculator>
277   Create(ExecutionSession &ES, RecordExecutionFunction RecordExec = {}) {
278     class make_shared_helper : public SimpleLazyReexportsSpeculator {
279     public:
280       make_shared_helper(ExecutionSession &ES,
281                          RecordExecutionFunction RecordExec)
282           : SimpleLazyReexportsSpeculator(ES, std::move(RecordExec)) {}
283     };
284 
285     auto Instance =
286         std::make_shared<make_shared_helper>(ES, std::move(RecordExec));
287     Instance->WeakThis = Instance;
288     return Instance;
289   }
290 
291   SimpleLazyReexportsSpeculator(SimpleLazyReexportsSpeculator &&) = delete;
292   SimpleLazyReexportsSpeculator &
293   operator=(SimpleLazyReexportsSpeculator &&) = delete;
294   ~SimpleLazyReexportsSpeculator() override;
295 
296   void onLazyReexportsCreated(JITDylib &JD, ResourceKey K,
297                               const SymbolAliasMap &Reexports) override;
298 
299   void onLazyReexportsTransfered(JITDylib &JD, ResourceKey DstK,
300                                  ResourceKey SrcK) override;
301 
302   Error onLazyReexportsRemoved(JITDylib &JD, ResourceKey K) override;
303 
304   void onLazyReexportCalled(const CallThroughInfo &CTI) override;
305 
306   void addSpeculationSuggestions(
307       std::vector<std::pair<std::string, SymbolStringPtr>> NewSuggestions);
308 
309 private:
310   SimpleLazyReexportsSpeculator(ExecutionSession &ES,
311                                 RecordExecutionFunction RecordExec)
312       : ES(ES), RecordExec(std::move(RecordExec)) {}
313 
314   bool doNextSpeculativeLookup();
315 
316   class SpeculateTask;
317 
318   using KeyToFunctionBodiesMap =
319       DenseMap<ResourceKey, std::vector<SymbolStringPtr>>;
320 
321   ExecutionSession &ES;
322   RecordExecutionFunction RecordExec;
323   std::weak_ptr<SimpleLazyReexportsSpeculator> WeakThis;
324   DenseMap<JITDylib *, KeyToFunctionBodiesMap> LazyReexports;
325   std::deque<std::pair<std::string, SymbolStringPtr>> SpeculateSuggestions;
326   bool SpeculateTaskActive = false;
327 };
328 
329 } // End namespace orc
330 } // End namespace llvm
331 
332 #endif // LLVM_EXECUTIONENGINE_ORC_LAZYREEXPORTS_H
333