1 | //===- LLJITWithExecutorProcessControl.cpp - LLJIT example with EPC utils -===// |
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 | // In this example we will use the lazy re-exports utility to lazily compile |
10 | // IR modules. We will do this in seven steps: |
11 | // |
12 | // 1. Create an LLJIT instance. |
13 | // 2. Install a transform so that we can see what is being compiled. |
14 | // 3. Create an indirect stubs manager and lazy call-through manager. |
15 | // 4. Add two modules that will be conditionally compiled, plus a main module. |
16 | // 5. Add lazy-rexports of the symbols in the conditionally compiled modules. |
17 | // 6. Dump the ExecutionSession state to see the symbol table prior to |
18 | // executing any code. |
19 | // 7. Verify that only modules containing executed code are compiled. |
20 | // |
21 | //===----------------------------------------------------------------------===// |
22 | |
23 | #include "llvm/ADT/StringMap.h" |
24 | #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h" |
25 | #include "llvm/ExecutionEngine/Orc/EPCIndirectionUtils.h" |
26 | #include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" |
27 | #include "llvm/ExecutionEngine/Orc/LLJIT.h" |
28 | #include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" |
29 | #include "llvm/ExecutionEngine/Orc/OrcABISupport.h" |
30 | #include "llvm/Support/InitLLVM.h" |
31 | #include "llvm/Support/TargetSelect.h" |
32 | #include "llvm/Support/raw_ostream.h" |
33 | |
34 | #include "../ExampleModules.h" |
35 | |
36 | #include <future> |
37 | |
38 | using namespace llvm; |
39 | using namespace llvm::orc; |
40 | |
41 | ExitOnError ExitOnErr; |
42 | |
43 | // Example IR modules. |
44 | // |
45 | // Note that in the conditionally compiled modules, FooMod and BarMod, functions |
46 | // have been given an _body suffix. This is to ensure that their names do not |
47 | // clash with their lazy-reexports. |
48 | // For clients who do not wish to rename function bodies (e.g. because they want |
49 | // to re-use cached objects between static and JIT compiles) techniques exist to |
50 | // avoid renaming. See the lazy-reexports section of the ORCv2 design doc. |
51 | |
52 | const llvm::StringRef FooMod = |
53 | R"( |
54 | declare i32 @return1() |
55 | |
56 | define i32 @foo_body() { |
57 | entry: |
58 | %0 = call i32 @return1() |
59 | ret i32 %0 |
60 | } |
61 | )" ; |
62 | |
63 | const llvm::StringRef BarMod = |
64 | R"( |
65 | declare i32 @return2() |
66 | |
67 | define i32 @bar_body() { |
68 | entry: |
69 | %0 = call i32 @return2() |
70 | ret i32 %0 |
71 | } |
72 | )" ; |
73 | |
74 | const llvm::StringRef MainMod = |
75 | R"( |
76 | |
77 | define i32 @entry(i32 %argc) { |
78 | entry: |
79 | %and = and i32 %argc, 1 |
80 | %tobool = icmp eq i32 %and, 0 |
81 | br i1 %tobool, label %if.end, label %if.then |
82 | |
83 | if.then: ; preds = %entry |
84 | %call = tail call i32 @foo() #2 |
85 | br label %return |
86 | |
87 | if.end: ; preds = %entry |
88 | %call1 = tail call i32 @bar() #2 |
89 | br label %return |
90 | |
91 | return: ; preds = %if.end, %if.then |
92 | %retval.0 = phi i32 [ %call, %if.then ], [ %call1, %if.end ] |
93 | ret i32 %retval.0 |
94 | } |
95 | |
96 | declare i32 @foo() |
97 | declare i32 @bar() |
98 | )" ; |
99 | |
100 | extern "C" int32_t return1() { return 1; } |
101 | extern "C" int32_t return2() { return 2; } |
102 | |
103 | static void *reenter(void *Ctx, void *TrampolineAddr) { |
104 | std::promise<void *> LandingAddressP; |
105 | auto LandingAddressF = LandingAddressP.get_future(); |
106 | |
107 | auto *EPCIU = static_cast<EPCIndirectionUtils *>(Ctx); |
108 | EPCIU->getLazyCallThroughManager().resolveTrampolineLandingAddress( |
109 | TrampolineAddr: ExecutorAddr::fromPtr(Ptr: TrampolineAddr), NotifyLandingResolved: [&](ExecutorAddr LandingAddress) { |
110 | LandingAddressP.set_value(LandingAddress.toPtr<void *>()); |
111 | }); |
112 | return LandingAddressF.get(); |
113 | } |
114 | |
115 | static void reportErrorAndExit() { |
116 | errs() << "Unable to lazily compile function. Exiting.\n" ; |
117 | exit(status: 1); |
118 | } |
119 | |
120 | cl::list<std::string> InputArgv(cl::Positional, |
121 | cl::desc("<program arguments>..." )); |
122 | |
123 | int main(int argc, char *argv[]) { |
124 | // Initialize LLVM. |
125 | InitLLVM X(argc, argv); |
126 | |
127 | InitializeNativeTarget(); |
128 | InitializeNativeTargetAsmPrinter(); |
129 | |
130 | cl::ParseCommandLineOptions(argc, argv, Overview: "LLJITWithLazyReexports" ); |
131 | ExitOnErr.setBanner(std::string(argv[0]) + ": " ); |
132 | |
133 | // (1) Create LLJIT instance. |
134 | auto EPC = ExitOnErr(SelfExecutorProcessControl::Create()); |
135 | auto J = ExitOnErr( |
136 | LLJITBuilder().setExecutorProcessControl(std::move(EPC)).create()); |
137 | |
138 | // (2) Install transform to print modules as they are compiled: |
139 | J->getIRTransformLayer().setTransform( |
140 | [](ThreadSafeModule TSM, |
141 | const MaterializationResponsibility &R) -> Expected<ThreadSafeModule> { |
142 | TSM.withModuleDo(F: [](Module &M) { dbgs() << "---Compiling---\n" << M; }); |
143 | return std::move(TSM); // Not a redundant move: fix build on gcc-7.5 |
144 | }); |
145 | |
146 | // (3) Create stubs and call-through managers: |
147 | auto EPCIU = ExitOnErr(EPCIndirectionUtils::Create(ES&: J->getExecutionSession())); |
148 | ExitOnErr(EPCIU->writeResolverBlock(ReentryFnAddr: ExecutorAddr::fromPtr(Ptr: &reenter), |
149 | ReentryCtxAddr: ExecutorAddr::fromPtr(Ptr: EPCIU.get()))); |
150 | EPCIU->createLazyCallThroughManager( |
151 | ES&: J->getExecutionSession(), ErrorHandlerAddr: ExecutorAddr::fromPtr(Ptr: &reportErrorAndExit)); |
152 | auto ISM = EPCIU->createIndirectStubsManager(); |
153 | |
154 | // (4) Add modules. |
155 | ExitOnErr(J->addIRModule(TSM: ExitOnErr(parseExampleModule(Source: FooMod, Name: "foo-mod" )))); |
156 | ExitOnErr(J->addIRModule(TSM: ExitOnErr(parseExampleModule(Source: BarMod, Name: "bar-mod" )))); |
157 | ExitOnErr(J->addIRModule(TSM: ExitOnErr(parseExampleModule(Source: MainMod, Name: "main-mod" )))); |
158 | |
159 | // (5) Add lazy reexports. |
160 | MangleAndInterner Mangle(J->getExecutionSession(), J->getDataLayout()); |
161 | SymbolAliasMap ReExports( |
162 | {{Mangle("foo" ), |
163 | {Mangle("foo_body" ), |
164 | JITSymbolFlags::Exported | JITSymbolFlags::Callable}}, |
165 | {Mangle("bar" ), |
166 | {Mangle("bar_body" ), |
167 | JITSymbolFlags::Exported | JITSymbolFlags::Callable}}}); |
168 | ExitOnErr(J->getMainJITDylib().define( |
169 | MU: lazyReexports(LCTManager&: EPCIU->getLazyCallThroughManager(), ISManager&: *ISM, |
170 | SourceJD&: J->getMainJITDylib(), CallableAliases: std::move(ReExports)))); |
171 | |
172 | // (6) Dump the ExecutionSession state. |
173 | dbgs() << "---Session state---\n" ; |
174 | J->getExecutionSession().dump(OS&: dbgs()); |
175 | dbgs() << "\n" ; |
176 | |
177 | // (7) Execute the JIT'd main function and pass the example's command line |
178 | // arguments unmodified. This should cause either ExampleMod1 or ExampleMod2 |
179 | // to be compiled, and either "1" or "2" returned depending on the number of |
180 | // arguments passed. |
181 | |
182 | // Look up the JIT'd function, cast it to a function pointer, then call it. |
183 | auto EntryAddr = ExitOnErr(J->lookup(UnmangledName: "entry" )); |
184 | auto *Entry = EntryAddr.toPtr<int(int)>(); |
185 | |
186 | int Result = Entry(argc); |
187 | outs() << "---Result---\n" |
188 | << "entry(" << argc << ") = " << Result << "\n" ; |
189 | |
190 | // Destroy the EPCIndirectionUtils utility. |
191 | ExitOnErr(EPCIU->cleanup()); |
192 | |
193 | return 0; |
194 | } |
195 | |