annotate llvm/docs/tutorial/BuildingAJIT2.rst @ 235:edfff9242030 cbc-llvm13

...
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Wed, 21 Jul 2021 11:30:30 +0900
parents 5f17cb93ff66
children c4bab56944e8
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 =====================================================================
anatofuz
parents:
diff changeset
2 Building a JIT: Adding Optimizations -- An introduction to ORC Layers
anatofuz
parents:
diff changeset
3 =====================================================================
anatofuz
parents:
diff changeset
4
anatofuz
parents:
diff changeset
5 .. contents::
anatofuz
parents:
diff changeset
6 :local:
anatofuz
parents:
diff changeset
7
anatofuz
parents:
diff changeset
8 **This tutorial is under active development. It is incomplete and details may
anatofuz
parents:
diff changeset
9 change frequently.** Nonetheless we invite you to try it out as it stands, and
anatofuz
parents:
diff changeset
10 we welcome any feedback.
anatofuz
parents:
diff changeset
11
anatofuz
parents:
diff changeset
12 Chapter 2 Introduction
anatofuz
parents:
diff changeset
13 ======================
anatofuz
parents:
diff changeset
14
anatofuz
parents:
diff changeset
15 **Warning: This tutorial is currently being updated to account for ORC API
anatofuz
parents:
diff changeset
16 changes. Only Chapters 1 and 2 are up-to-date.**
anatofuz
parents:
diff changeset
17
anatofuz
parents:
diff changeset
18 **Example code from Chapters 3 to 5 will compile and run, but has not been
anatofuz
parents:
diff changeset
19 updated**
anatofuz
parents:
diff changeset
20
anatofuz
parents:
diff changeset
21 Welcome to Chapter 2 of the "Building an ORC-based JIT in LLVM" tutorial. In
anatofuz
parents:
diff changeset
22 `Chapter 1 <BuildingAJIT1.html>`_ of this series we examined a basic JIT
anatofuz
parents:
diff changeset
23 class, KaleidoscopeJIT, that could take LLVM IR modules as input and produce
anatofuz
parents:
diff changeset
24 executable code in memory. KaleidoscopeJIT was able to do this with relatively
anatofuz
parents:
diff changeset
25 little code by composing two off-the-shelf *ORC layers*: IRCompileLayer and
anatofuz
parents:
diff changeset
26 ObjectLinkingLayer, to do much of the heavy lifting.
anatofuz
parents:
diff changeset
27
anatofuz
parents:
diff changeset
28 In this layer we'll learn more about the ORC layer concept by using a new layer,
anatofuz
parents:
diff changeset
29 IRTransformLayer, to add IR optimization support to KaleidoscopeJIT.
anatofuz
parents:
diff changeset
30
anatofuz
parents:
diff changeset
31 Optimizing Modules using the IRTransformLayer
anatofuz
parents:
diff changeset
32 =============================================
anatofuz
parents:
diff changeset
33
anatofuz
parents:
diff changeset
34 In `Chapter 4 <LangImpl04.html>`_ of the "Implementing a language with LLVM"
anatofuz
parents:
diff changeset
35 tutorial series the llvm *FunctionPassManager* is introduced as a means for
anatofuz
parents:
diff changeset
36 optimizing LLVM IR. Interested readers may read that chapter for details, but
anatofuz
parents:
diff changeset
37 in short: to optimize a Module we create an llvm::FunctionPassManager
anatofuz
parents:
diff changeset
38 instance, configure it with a set of optimizations, then run the PassManager on
anatofuz
parents:
diff changeset
39 a Module to mutate it into a (hopefully) more optimized but semantically
anatofuz
parents:
diff changeset
40 equivalent form. In the original tutorial series the FunctionPassManager was
anatofuz
parents:
diff changeset
41 created outside the KaleidoscopeJIT and modules were optimized before being
anatofuz
parents:
diff changeset
42 added to it. In this Chapter we will make optimization a phase of our JIT
anatofuz
parents:
diff changeset
43 instead. For now this will provide us a motivation to learn more about ORC
anatofuz
parents:
diff changeset
44 layers, but in the long term making optimization part of our JIT will yield an
anatofuz
parents:
diff changeset
45 important benefit: When we begin lazily compiling code (i.e. deferring
anatofuz
parents:
diff changeset
46 compilation of each function until the first time it's run) having
anatofuz
parents:
diff changeset
47 optimization managed by our JIT will allow us to optimize lazily too, rather
anatofuz
parents:
diff changeset
48 than having to do all our optimization up-front.
anatofuz
parents:
diff changeset
49
anatofuz
parents:
diff changeset
50 To add optimization support to our JIT we will take the KaleidoscopeJIT from
anatofuz
parents:
diff changeset
51 Chapter 1 and compose an ORC *IRTransformLayer* on top. We will look at how the
anatofuz
parents:
diff changeset
52 IRTransformLayer works in more detail below, but the interface is simple: the
anatofuz
parents:
diff changeset
53 constructor for this layer takes a reference to the execution session and the
anatofuz
parents:
diff changeset
54 layer below (as all layers do) plus an *IR optimization function* that it will
anatofuz
parents:
diff changeset
55 apply to each Module that is added via addModule:
anatofuz
parents:
diff changeset
56
anatofuz
parents:
diff changeset
57 .. code-block:: c++
anatofuz
parents:
diff changeset
58
anatofuz
parents:
diff changeset
59 class KaleidoscopeJIT {
anatofuz
parents:
diff changeset
60 private:
anatofuz
parents:
diff changeset
61 ExecutionSession ES;
anatofuz
parents:
diff changeset
62 RTDyldObjectLinkingLayer ObjectLayer;
anatofuz
parents:
diff changeset
63 IRCompileLayer CompileLayer;
anatofuz
parents:
diff changeset
64 IRTransformLayer TransformLayer;
anatofuz
parents:
diff changeset
65
anatofuz
parents:
diff changeset
66 DataLayout DL;
anatofuz
parents:
diff changeset
67 MangleAndInterner Mangle;
anatofuz
parents:
diff changeset
68 ThreadSafeContext Ctx;
anatofuz
parents:
diff changeset
69
anatofuz
parents:
diff changeset
70 public:
anatofuz
parents:
diff changeset
71
anatofuz
parents:
diff changeset
72 KaleidoscopeJIT(JITTargetMachineBuilder JTMB, DataLayout DL)
anatofuz
parents:
diff changeset
73 : ObjectLayer(ES,
anatofuz
parents:
diff changeset
74 []() { return std::make_unique<SectionMemoryManager>(); }),
anatofuz
parents:
diff changeset
75 CompileLayer(ES, ObjectLayer, ConcurrentIRCompiler(std::move(JTMB))),
anatofuz
parents:
diff changeset
76 TransformLayer(ES, CompileLayer, optimizeModule),
anatofuz
parents:
diff changeset
77 DL(std::move(DL)), Mangle(ES, this->DL),
anatofuz
parents:
diff changeset
78 Ctx(std::make_unique<LLVMContext>()) {
221
79ff65ed7e25 LLVM12 Original
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 173
diff changeset
79 ES.getMainJITDylib().addGenerator(
79ff65ed7e25 LLVM12 Original
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 173
diff changeset
80 cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(DL.getGlobalPrefix())));
150
anatofuz
parents:
diff changeset
81 }
anatofuz
parents:
diff changeset
82
anatofuz
parents:
diff changeset
83 Our extended KaleidoscopeJIT class starts out the same as it did in Chapter 1,
anatofuz
parents:
diff changeset
84 but after the CompileLayer we introduce a new member, TransformLayer, which sits
anatofuz
parents:
diff changeset
85 on top of our CompileLayer. We initialize our OptimizeLayer with a reference to
anatofuz
parents:
diff changeset
86 the ExecutionSession and output layer (standard practice for layers), along with
anatofuz
parents:
diff changeset
87 a *transform function*. For our transform function we supply our classes
anatofuz
parents:
diff changeset
88 optimizeModule static method.
anatofuz
parents:
diff changeset
89
anatofuz
parents:
diff changeset
90 .. code-block:: c++
anatofuz
parents:
diff changeset
91
anatofuz
parents:
diff changeset
92 // ...
anatofuz
parents:
diff changeset
93 return cantFail(OptimizeLayer.addModule(std::move(M),
anatofuz
parents:
diff changeset
94 std::move(Resolver)));
anatofuz
parents:
diff changeset
95 // ...
anatofuz
parents:
diff changeset
96
anatofuz
parents:
diff changeset
97 Next we need to update our addModule method to replace the call to
anatofuz
parents:
diff changeset
98 ``CompileLayer::add`` with a call to ``OptimizeLayer::add`` instead.
anatofuz
parents:
diff changeset
99
anatofuz
parents:
diff changeset
100 .. code-block:: c++
anatofuz
parents:
diff changeset
101
anatofuz
parents:
diff changeset
102 static Expected<ThreadSafeModule>
anatofuz
parents:
diff changeset
103 optimizeModule(ThreadSafeModule M, const MaterializationResponsibility &R) {
anatofuz
parents:
diff changeset
104 // Create a function pass manager.
anatofuz
parents:
diff changeset
105 auto FPM = std::make_unique<legacy::FunctionPassManager>(M.get());
anatofuz
parents:
diff changeset
106
anatofuz
parents:
diff changeset
107 // Add some optimizations.
anatofuz
parents:
diff changeset
108 FPM->add(createInstructionCombiningPass());
anatofuz
parents:
diff changeset
109 FPM->add(createReassociatePass());
anatofuz
parents:
diff changeset
110 FPM->add(createGVNPass());
anatofuz
parents:
diff changeset
111 FPM->add(createCFGSimplificationPass());
anatofuz
parents:
diff changeset
112 FPM->doInitialization();
anatofuz
parents:
diff changeset
113
anatofuz
parents:
diff changeset
114 // Run the optimizations over all functions in the module being added to
anatofuz
parents:
diff changeset
115 // the JIT.
anatofuz
parents:
diff changeset
116 for (auto &F : *M)
anatofuz
parents:
diff changeset
117 FPM->run(F);
anatofuz
parents:
diff changeset
118
anatofuz
parents:
diff changeset
119 return M;
anatofuz
parents:
diff changeset
120 }
anatofuz
parents:
diff changeset
121
anatofuz
parents:
diff changeset
122 At the bottom of our JIT we add a private method to do the actual optimization:
anatofuz
parents:
diff changeset
123 *optimizeModule*. This function takes the module to be transformed as input (as
anatofuz
parents:
diff changeset
124 a ThreadSafeModule) along with a reference to a reference to a new class:
anatofuz
parents:
diff changeset
125 ``MaterializationResponsibility``. The MaterializationResponsibility argument
anatofuz
parents:
diff changeset
126 can be used to query JIT state for the module being transformed, such as the set
anatofuz
parents:
diff changeset
127 of definitions in the module that JIT'd code is actively trying to call/access.
anatofuz
parents:
diff changeset
128 For now we will ignore this argument and use a standard optimization
anatofuz
parents:
diff changeset
129 pipeline. To do this we set up a FunctionPassManager, add some passes to it, run
anatofuz
parents:
diff changeset
130 it over every function in the module, and then return the mutated module. The
anatofuz
parents:
diff changeset
131 specific optimizations are the same ones used in `Chapter 4 <LangImpl04.html>`_
anatofuz
parents:
diff changeset
132 of the "Implementing a language with LLVM" tutorial series. Readers may visit
anatofuz
parents:
diff changeset
133 that chapter for a more in-depth discussion of these, and of IR optimization in
anatofuz
parents:
diff changeset
134 general.
anatofuz
parents:
diff changeset
135
anatofuz
parents:
diff changeset
136 And that's it in terms of changes to KaleidoscopeJIT: When a module is added via
anatofuz
parents:
diff changeset
137 addModule the OptimizeLayer will call our optimizeModule function before passing
anatofuz
parents:
diff changeset
138 the transformed module on to the CompileLayer below. Of course, we could have
anatofuz
parents:
diff changeset
139 called optimizeModule directly in our addModule function and not gone to the
anatofuz
parents:
diff changeset
140 bother of using the IRTransformLayer, but doing so gives us another opportunity
anatofuz
parents:
diff changeset
141 to see how layers compose. It also provides a neat entry point to the *layer*
anatofuz
parents:
diff changeset
142 concept itself, because IRTransformLayer is one of the simplest layers that
anatofuz
parents:
diff changeset
143 can be implemented.
anatofuz
parents:
diff changeset
144
anatofuz
parents:
diff changeset
145 .. code-block:: c++
anatofuz
parents:
diff changeset
146
anatofuz
parents:
diff changeset
147 // From IRTransformLayer.h:
anatofuz
parents:
diff changeset
148 class IRTransformLayer : public IRLayer {
anatofuz
parents:
diff changeset
149 public:
anatofuz
parents:
diff changeset
150 using TransformFunction = std::function<Expected<ThreadSafeModule>(
anatofuz
parents:
diff changeset
151 ThreadSafeModule, const MaterializationResponsibility &R)>;
anatofuz
parents:
diff changeset
152
anatofuz
parents:
diff changeset
153 IRTransformLayer(ExecutionSession &ES, IRLayer &BaseLayer,
anatofuz
parents:
diff changeset
154 TransformFunction Transform = identityTransform);
anatofuz
parents:
diff changeset
155
anatofuz
parents:
diff changeset
156 void setTransform(TransformFunction Transform) {
anatofuz
parents:
diff changeset
157 this->Transform = std::move(Transform);
anatofuz
parents:
diff changeset
158 }
anatofuz
parents:
diff changeset
159
anatofuz
parents:
diff changeset
160 static ThreadSafeModule
anatofuz
parents:
diff changeset
161 identityTransform(ThreadSafeModule TSM,
anatofuz
parents:
diff changeset
162 const MaterializationResponsibility &R) {
anatofuz
parents:
diff changeset
163 return TSM;
anatofuz
parents:
diff changeset
164 }
anatofuz
parents:
diff changeset
165
anatofuz
parents:
diff changeset
166 void emit(MaterializationResponsibility R, ThreadSafeModule TSM) override;
anatofuz
parents:
diff changeset
167
anatofuz
parents:
diff changeset
168 private:
anatofuz
parents:
diff changeset
169 IRLayer &BaseLayer;
anatofuz
parents:
diff changeset
170 TransformFunction Transform;
anatofuz
parents:
diff changeset
171 };
anatofuz
parents:
diff changeset
172
anatofuz
parents:
diff changeset
173 // From IRTransformLayer.cpp:
anatofuz
parents:
diff changeset
174
anatofuz
parents:
diff changeset
175 IRTransformLayer::IRTransformLayer(ExecutionSession &ES,
anatofuz
parents:
diff changeset
176 IRLayer &BaseLayer,
anatofuz
parents:
diff changeset
177 TransformFunction Transform)
anatofuz
parents:
diff changeset
178 : IRLayer(ES), BaseLayer(BaseLayer), Transform(std::move(Transform)) {}
anatofuz
parents:
diff changeset
179
anatofuz
parents:
diff changeset
180 void IRTransformLayer::emit(MaterializationResponsibility R,
anatofuz
parents:
diff changeset
181 ThreadSafeModule TSM) {
anatofuz
parents:
diff changeset
182 assert(TSM.getModule() && "Module must not be null");
anatofuz
parents:
diff changeset
183
anatofuz
parents:
diff changeset
184 if (auto TransformedTSM = Transform(std::move(TSM), R))
anatofuz
parents:
diff changeset
185 BaseLayer.emit(std::move(R), std::move(*TransformedTSM));
anatofuz
parents:
diff changeset
186 else {
anatofuz
parents:
diff changeset
187 R.failMaterialization();
anatofuz
parents:
diff changeset
188 getExecutionSession().reportError(TransformedTSM.takeError());
anatofuz
parents:
diff changeset
189 }
anatofuz
parents:
diff changeset
190 }
anatofuz
parents:
diff changeset
191
anatofuz
parents:
diff changeset
192 This is the whole definition of IRTransformLayer, from
anatofuz
parents:
diff changeset
193 ``llvm/include/llvm/ExecutionEngine/Orc/IRTransformLayer.h`` and
anatofuz
parents:
diff changeset
194 ``llvm/lib/ExecutionEngine/Orc/IRTransformLayer.cpp``. This class is concerned
anatofuz
parents:
diff changeset
195 with two very simple jobs: (1) Running every IR Module that is emitted via this
anatofuz
parents:
diff changeset
196 layer through the transform function object, and (2) implementing the ORC
anatofuz
parents:
diff changeset
197 ``IRLayer`` interface (which itself conforms to the general ORC Layer concept,
anatofuz
parents:
diff changeset
198 more on that below). Most of the class is straightforward: a typedef for the
anatofuz
parents:
diff changeset
199 transform function, a constructor to initialize the members, a setter for the
anatofuz
parents:
diff changeset
200 transform function value, and a default no-op transform. The most important
anatofuz
parents:
diff changeset
201 method is ``emit`` as this is half of our IRLayer interface. The emit method
anatofuz
parents:
diff changeset
202 applies our transform to each module that it is called on and, if the transform
anatofuz
parents:
diff changeset
203 succeeds, passes the transformed module to the base layer. If the transform
anatofuz
parents:
diff changeset
204 fails, our emit function calls
anatofuz
parents:
diff changeset
205 ``MaterializationResponsibility::failMaterialization`` (this JIT clients who
anatofuz
parents:
diff changeset
206 may be waiting on other threads know that the code they were waiting for has
anatofuz
parents:
diff changeset
207 failed to compile) and logs the error with the execution session before bailing
anatofuz
parents:
diff changeset
208 out.
anatofuz
parents:
diff changeset
209
anatofuz
parents:
diff changeset
210 The other half of the IRLayer interface we inherit unmodified from the IRLayer
anatofuz
parents:
diff changeset
211 class:
anatofuz
parents:
diff changeset
212
anatofuz
parents:
diff changeset
213 .. code-block:: c++
anatofuz
parents:
diff changeset
214
anatofuz
parents:
diff changeset
215 Error IRLayer::add(JITDylib &JD, ThreadSafeModule TSM, VModuleKey K) {
anatofuz
parents:
diff changeset
216 return JD.define(std::make_unique<BasicIRLayerMaterializationUnit>(
anatofuz
parents:
diff changeset
217 *this, std::move(K), std::move(TSM)));
anatofuz
parents:
diff changeset
218 }
anatofuz
parents:
diff changeset
219
anatofuz
parents:
diff changeset
220 This code, from ``llvm/lib/ExecutionEngine/Orc/Layer.cpp``, adds a
anatofuz
parents:
diff changeset
221 ThreadSafeModule to a given JITDylib by wrapping it up in a
anatofuz
parents:
diff changeset
222 ``MaterializationUnit`` (in this case a ``BasicIRLayerMaterializationUnit``).
anatofuz
parents:
diff changeset
223 Most layers that derived from IRLayer can rely on this default implementation
anatofuz
parents:
diff changeset
224 of the ``add`` method.
anatofuz
parents:
diff changeset
225
anatofuz
parents:
diff changeset
226 These two operations, ``add`` and ``emit``, together constitute the layer
223
5f17cb93ff66 LLVM13 (2021/7/18)
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 221
diff changeset
227 concept: A layer is a way to wrap a part of a compiler pipeline (in this case
5f17cb93ff66 LLVM13 (2021/7/18)
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 221
diff changeset
228 the "opt" phase of an LLVM compiler) whose API is opaque to ORC with an
5f17cb93ff66 LLVM13 (2021/7/18)
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 221
diff changeset
229 interface that ORC can call as needed. The add method takes an
150
anatofuz
parents:
diff changeset
230 module in some input program representation (in this case an LLVM IR module) and
anatofuz
parents:
diff changeset
231 stores it in the target JITDylib, arranging for it to be passed back to the
anatofuz
parents:
diff changeset
232 Layer's emit method when any symbol defined by that module is requested. Layers
anatofuz
parents:
diff changeset
233 can compose neatly by calling the 'emit' method of a base layer to complete
anatofuz
parents:
diff changeset
234 their work. For example, in this tutorial our IRTransformLayer calls through to
anatofuz
parents:
diff changeset
235 our IRCompileLayer to compile the transformed IR, and our IRCompileLayer in turn
anatofuz
parents:
diff changeset
236 calls our ObjectLayer to link the object file produced by our compiler.
anatofuz
parents:
diff changeset
237
anatofuz
parents:
diff changeset
238
anatofuz
parents:
diff changeset
239 So far we have learned how to optimize and compile our LLVM IR, but we have not
anatofuz
parents:
diff changeset
240 focused on when compilation happens. Our current REPL is eager: Each function
anatofuz
parents:
diff changeset
241 definition is optimized and compiled as soon as it is referenced by any other
anatofuz
parents:
diff changeset
242 code, regardless of whether it is ever called at runtime. In the next chapter we
anatofuz
parents:
diff changeset
243 will introduce fully lazy compilation, in which functions are not compiled until
anatofuz
parents:
diff changeset
244 they are first called at run-time. At this point the trade-offs get much more
anatofuz
parents:
diff changeset
245 interesting: the lazier we are, the quicker we can start executing the first
anatofuz
parents:
diff changeset
246 function, but the more often we will have to pause to compile newly encountered
anatofuz
parents:
diff changeset
247 functions. If we only code-gen lazily, but optimize eagerly, we will have a
anatofuz
parents:
diff changeset
248 longer startup time (as everything is optimized) but relatively short pauses as
anatofuz
parents:
diff changeset
249 each function just passes through code-gen. If we both optimize and code-gen
anatofuz
parents:
diff changeset
250 lazily we can start executing the first function more quickly, but we will have
anatofuz
parents:
diff changeset
251 longer pauses as each function has to be both optimized and code-gen'd when it
anatofuz
parents:
diff changeset
252 is first executed. Things become even more interesting if we consider
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
253 interprocedural optimizations like inlining, which must be performed eagerly.
150
anatofuz
parents:
diff changeset
254 These are complex trade-offs, and there is no one-size-fits all solution to
anatofuz
parents:
diff changeset
255 them, but by providing composable layers we leave the decisions to the person
anatofuz
parents:
diff changeset
256 implementing the JIT, and make it easy for them to experiment with different
anatofuz
parents:
diff changeset
257 configurations.
anatofuz
parents:
diff changeset
258
anatofuz
parents:
diff changeset
259 `Next: Adding Per-function Lazy Compilation <BuildingAJIT3.html>`_
anatofuz
parents:
diff changeset
260
anatofuz
parents:
diff changeset
261 Full Code Listing
anatofuz
parents:
diff changeset
262 =================
anatofuz
parents:
diff changeset
263
anatofuz
parents:
diff changeset
264 Here is the complete code listing for our running example with an
anatofuz
parents:
diff changeset
265 IRTransformLayer added to enable optimization. To build this example, use:
anatofuz
parents:
diff changeset
266
anatofuz
parents:
diff changeset
267 .. code-block:: bash
anatofuz
parents:
diff changeset
268
anatofuz
parents:
diff changeset
269 # Compile
anatofuz
parents:
diff changeset
270 clang++ -g toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core orcjit native` -O3 -o toy
anatofuz
parents:
diff changeset
271 # Run
anatofuz
parents:
diff changeset
272 ./toy
anatofuz
parents:
diff changeset
273
anatofuz
parents:
diff changeset
274 Here is the code:
anatofuz
parents:
diff changeset
275
anatofuz
parents:
diff changeset
276 .. literalinclude:: ../../examples/Kaleidoscope/BuildingAJIT/Chapter2/KaleidoscopeJIT.h
anatofuz
parents:
diff changeset
277 :language: c++