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