blob: 7d25ccaba0aa1ff24f2df272f8f854d864935ab5 [file] [log] [blame]
Lang Hamese038aae2016-06-06 18:35:44 +00001=====================================================================
2Building a JIT: Adding Optimizations -- An introduction to ORC Layers
3=====================================================================
Lang Hamesbe84d2be2016-05-26 00:38:04 +00004
5.. contents::
6 :local:
7
8**This tutorial is under active development. It is incomplete and details may
9change frequently.** Nonetheless we invite you to try it out as it stands, and
10we welcome any feedback.
11
12Chapter 2 Introduction
13======================
14
Lang Hames575515f2018-11-13 01:25:34 +000015**Warning: This tutorial is currently being updated to account for ORC API
16changes. Only Chapters 1 and 2 are up-to-date.**
Lang Hamesf3fb98362018-02-06 21:25:20 +000017
Lang Hames575515f2018-11-13 01:25:34 +000018**Example code from Chapters 3 to 5 will compile and run, but has not been
19updated**
Lang Hamesf3fb98362018-02-06 21:25:20 +000020
Lang Hamesc499d2a2016-06-06 03:28:12 +000021Welcome to Chapter 2 of the "Building an ORC-based JIT in LLVM" tutorial. In
22`Chapter 1 <BuildingAJIT1.html>`_ of this series we examined a basic JIT
23class, KaleidoscopeJIT, that could take LLVM IR modules as input and produce
24executable code in memory. KaleidoscopeJIT was able to do this with relatively
25little code by composing two off-the-shelf *ORC layers*: IRCompileLayer and
26ObjectLinkingLayer, to do much of the heavy lifting.
Lang Hamesbe84d2be2016-05-26 00:38:04 +000027
Lang Hamesc499d2a2016-06-06 03:28:12 +000028In this layer we'll learn more about the ORC layer concept by using a new layer,
29IRTransformLayer, to add IR optimization support to KaleidoscopeJIT.
Lang Hamesbe84d2be2016-05-26 00:38:04 +000030
Lang Hamesc499d2a2016-06-06 03:28:12 +000031Optimizing Modules using the IRTransformLayer
32=============================================
Lang Hamesbe84d2be2016-05-26 00:38:04 +000033
Kirill Bobyreve4364832017-07-10 09:07:23 +000034In `Chapter 4 <LangImpl04.html>`_ of the "Implementing a language with LLVM"
Lang Hamesc499d2a2016-06-06 03:28:12 +000035tutorial series the llvm *FunctionPassManager* is introduced as a means for
36optimizing LLVM IR. Interested readers may read that chapter for details, but
Lang Hames8705d112016-06-20 18:34:46 +000037in short: to optimize a Module we create an llvm::FunctionPassManager
Lang Hamesc499d2a2016-06-06 03:28:12 +000038instance, configure it with a set of optimizations, then run the PassManager on
39a Module to mutate it into a (hopefully) more optimized but semantically
40equivalent form. In the original tutorial series the FunctionPassManager was
Lang Hames11c43d52016-06-20 18:37:52 +000041created outside the KaleidoscopeJIT and modules were optimized before being
Lang Hamesc499d2a2016-06-06 03:28:12 +000042added to it. In this Chapter we will make optimization a phase of our JIT
Lang Hames11c43d52016-06-20 18:37:52 +000043instead. For now this will provide us a motivation to learn more about ORC
Lang Hamesc499d2a2016-06-06 03:28:12 +000044layers, but in the long term making optimization part of our JIT will yield an
45important benefit: When we begin lazily compiling code (i.e. deferring
Lang Hames575515f2018-11-13 01:25:34 +000046compilation of each function until the first time it's run) having
Lang Hamesc499d2a2016-06-06 03:28:12 +000047optimization managed by our JIT will allow us to optimize lazily too, rather
48than having to do all our optimization up-front.
Lang Hamesbe84d2be2016-05-26 00:38:04 +000049
Lang Hamesc499d2a2016-06-06 03:28:12 +000050To add optimization support to our JIT we will take the KaleidoscopeJIT from
51Chapter 1 and compose an ORC *IRTransformLayer* on top. We will look at how the
52IRTransformLayer works in more detail below, but the interface is simple: the
Lang Hames575515f2018-11-13 01:25:34 +000053constructor for this layer takes a reference to the execution session and the
54layer below (as all layers do) plus an *IR optimization function* that it will
55apply to each Module that is added via addModule:
Lang Hamesc499d2a2016-06-06 03:28:12 +000056
Lang Hames38eb0312016-06-06 04:53:59 +000057.. code-block:: c++
Lang Hamesc499d2a2016-06-06 03:28:12 +000058
59 class KaleidoscopeJIT {
60 private:
Lang Hames575515f2018-11-13 01:25:34 +000061 ExecutionSession ES;
62 RTDyldObjectLinkingLayer ObjectLayer;
63 IRCompileLayer CompileLayer;
64 IRTransformLayer TransformLayer;
Lang Hamesc499d2a2016-06-06 03:28:12 +000065
Lang Hames575515f2018-11-13 01:25:34 +000066 DataLayout DL;
67 MangleAndInterner Mangle;
68 ThreadSafeContext Ctx;
Lang Hamesc499d2a2016-06-06 03:28:12 +000069
70 public:
Lang Hamesc499d2a2016-06-06 03:28:12 +000071
Lang Hames575515f2018-11-13 01:25:34 +000072 KaleidoscopeJIT(JITTargetMachineBuilder JTMB, DataLayout DL)
73 : ObjectLayer(ES,
74 []() { return llvm::make_unique<SectionMemoryManager>(); }),
75 CompileLayer(ES, ObjectLayer, ConcurrentIRCompiler(std::move(JTMB))),
76 TransformLayer(ES, CompileLayer, optimizeModule),
77 DL(std::move(DL)), Mangle(ES, this->DL),
78 Ctx(llvm::make_unique<LLVMContext>()) {
79 ES.getMainJITDylib().setGenerator(
80 cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(DL)));
Lang Hamesc499d2a2016-06-06 03:28:12 +000081 }
82
83Our extended KaleidoscopeJIT class starts out the same as it did in Chapter 1,
Lang Hames575515f2018-11-13 01:25:34 +000084but after the CompileLayer we introduce a new member, TransformLayer, which sits
85on top of our CompileLayer. We initialize our OptimizeLayer with a reference to
86the ExecutionSession and output layer (standard practice for layers), along with
87a *transform function*. For our transform function we supply our classes
88optimizeModule static method.
Lang Hames3242f652016-06-06 05:07:52 +000089
90.. code-block:: c++
91
92 // ...
Don Hinton4b93d232017-09-17 00:24:43 +000093 return cantFail(OptimizeLayer.addModule(std::move(M),
94 std::move(Resolver)));
Lang Hamesc499d2a2016-06-06 03:28:12 +000095 // ...
96
Lang Hames575515f2018-11-13 01:25:34 +000097Next we need to update our addModule method to replace the call to
98``CompileLayer::add`` with a call to ``OptimizeLayer::add`` instead.
Lang Hamesc499d2a2016-06-06 03:28:12 +000099
Lang Hames3242f652016-06-06 05:07:52 +0000100.. code-block:: c++
101
Lang Hames8bf69be2018-11-13 01:26:25 +0000102 static Expected<ThreadSafeModule>
103 optimizeModule(ThreadSafeModule M, const MaterializationResponsibility &R) {
Lang Hamesc499d2a2016-06-06 03:28:12 +0000104 // Create a function pass manager.
105 auto FPM = llvm::make_unique<legacy::FunctionPassManager>(M.get());
106
107 // Add some optimizations.
108 FPM->add(createInstructionCombiningPass());
109 FPM->add(createReassociatePass());
110 FPM->add(createGVNPass());
111 FPM->add(createCFGSimplificationPass());
112 FPM->doInitialization();
113
114 // Run the optimizations over all functions in the module being added to
115 // the JIT.
116 for (auto &F : *M)
117 FPM->run(F);
118
119 return M;
120 }
121
122At the bottom of our JIT we add a private method to do the actual optimization:
Lang Hames575515f2018-11-13 01:25:34 +0000123*optimizeModule*. This function takes the module to be transformed as input (as
124a ThreadSafeModule) along with a reference to a reference to a new class:
125``MaterializationResponsibility``. The MaterializationResponsibility argument
126can be used to query JIT state for the module being transformed, such as the set
127of definitions in the module that JIT'd code is actively trying to call/access.
128For now we will ignore this argument and use a standard optimization
129pipeline. To do this we set up a FunctionPassManager, add some passes to it, run
130it over every function in the module, and then return the mutated module. The
131specific optimizations are the same ones used in `Chapter 4 <LangImpl04.html>`_
132of the "Implementing a language with LLVM" tutorial series. Readers may visit
133that chapter for a more in-depth discussion of these, and of IR optimization in
134general.
Lang Hamesc499d2a2016-06-06 03:28:12 +0000135
Lang Hamesd29ee532016-06-06 18:22:47 +0000136And that's it in terms of changes to KaleidoscopeJIT: When a module is added via
137addModule the OptimizeLayer will call our optimizeModule function before passing
138the transformed module on to the CompileLayer below. Of course, we could have
139called optimizeModule directly in our addModule function and not gone to the
140bother of using the IRTransformLayer, but doing so gives us another opportunity
141to see how layers compose. It also provides a neat entry point to the *layer*
Lang Hames575515f2018-11-13 01:25:34 +0000142concept itself, because IRTransformLayer is one of the simplest layers that
143can be implemented.
Lang Hamesc499d2a2016-06-06 03:28:12 +0000144
Lang Hames38eb0312016-06-06 04:53:59 +0000145.. code-block:: c++
Lang Hamesc499d2a2016-06-06 03:28:12 +0000146
Lang Hames575515f2018-11-13 01:25:34 +0000147 // From IRTransformLayer.h:
148 class IRTransformLayer : public IRLayer {
Lang Hamesc499d2a2016-06-06 03:28:12 +0000149 public:
Lang Hames575515f2018-11-13 01:25:34 +0000150 using TransformFunction = std::function<Expected<ThreadSafeModule>(
151 ThreadSafeModule, const MaterializationResponsibility &R)>;
Lang Hamesc499d2a2016-06-06 03:28:12 +0000152
Lang Hames575515f2018-11-13 01:25:34 +0000153 IRTransformLayer(ExecutionSession &ES, IRLayer &BaseLayer,
154 TransformFunction Transform = identityTransform);
Lang Hamesc499d2a2016-06-06 03:28:12 +0000155
Lang Hames575515f2018-11-13 01:25:34 +0000156 void setTransform(TransformFunction Transform) {
157 this->Transform = std::move(Transform);
Lang Hamesc499d2a2016-06-06 03:28:12 +0000158 }
159
Lang Hames575515f2018-11-13 01:25:34 +0000160 static ThreadSafeModule
161 identityTransform(ThreadSafeModule TSM,
162 const MaterializationResponsibility &R) {
163 return TSM;
Lang Hamesc499d2a2016-06-06 03:28:12 +0000164 }
165
Lang Hames575515f2018-11-13 01:25:34 +0000166 void emit(MaterializationResponsibility R, ThreadSafeModule TSM) override;
Lang Hamesc499d2a2016-06-06 03:28:12 +0000167
168 private:
Lang Hames575515f2018-11-13 01:25:34 +0000169 IRLayer &BaseLayer;
170 TransformFunction Transform;
Lang Hamesc499d2a2016-06-06 03:28:12 +0000171 };
172
Lang Hames575515f2018-11-13 01:25:34 +0000173 // From IRTransfomrLayer.cpp:
174
175 IRTransformLayer::IRTransformLayer(ExecutionSession &ES,
176 IRLayer &BaseLayer,
177 TransformFunction Transform)
178 : IRLayer(ES), BaseLayer(BaseLayer), Transform(std::move(Transform)) {}
179
180 void IRTransformLayer::emit(MaterializationResponsibility R,
181 ThreadSafeModule TSM) {
182 assert(TSM.getModule() && "Module must not be null");
183
184 if (auto TransformedTSM = Transform(std::move(TSM), R))
185 BaseLayer.emit(std::move(R), std::move(*TransformedTSM));
186 else {
187 R.failMaterialization();
188 getExecutionSession().reportError(TransformedTSM.takeError());
189 }
190 }
191
Lang Hamesc499d2a2016-06-06 03:28:12 +0000192This is the whole definition of IRTransformLayer, from
Lang Hames575515f2018-11-13 01:25:34 +0000193``llvm/include/llvm/ExecutionEngine/Orc/IRTransformLayer.h`` and
194``llvm/lib/ExecutionEngine/Orc/IRTransformLayer.cpp``. This class is concerned
195with two very simple jobs: (1) Running every IR Module that is emitted via this
196layer through the transform function object, and (2) implementing the ORC
197``IRLayer`` interface (which itself conforms to the general ORC Layer concept,
198more on that below). Most of the class is straightforward: a typedef for the
199transform function, a constructor to initialize the members, a setter for the
200transform function value, and a default no-op transform. The most important
201method is ``emit`` as this is half of our IRLayer interface. The emit method
202applies our transform to each module that it is called on and, if the transform
203succeeds, passes the transformed module to the base layer. If the transform
204fails, our emit function calls
205``MaterializationResponsibility::failMaterialization`` (this JIT clients who
206may be waiting on other threads know that the code they were waiting for has
207failed to compile) and logs the error with the execution session before bailing
208out.
Lang Hamesc499d2a2016-06-06 03:28:12 +0000209
Lang Hames575515f2018-11-13 01:25:34 +0000210The other half of the IRLayer interface we inherit unmodified from the IRLayer
211class:
Lang Hamesc499d2a2016-06-06 03:28:12 +0000212
Lang Hames575515f2018-11-13 01:25:34 +0000213.. code-block:: c++
Lang Hamesc499d2a2016-06-06 03:28:12 +0000214
Lang Hames575515f2018-11-13 01:25:34 +0000215 Error IRLayer::add(JITDylib &JD, ThreadSafeModule TSM, VModuleKey K) {
216 return JD.define(llvm::make_unique<BasicIRLayerMaterializationUnit>(
217 *this, std::move(K), std::move(TSM)));
218 }
219
220This code, from ``llvm/lib/ExecutionEngine/Orc/Layer.cpp``, adds a
221ThreadSafeModule to a given JITDylib by wrapping it up in a
222``MaterializationUnit`` (in this case a ``BasicIRLayerMaterializationUnit``).
223Most layers that derived from IRLayer can rely on this default implementation
224of the ``add`` method.
225
226These two operations, ``add`` and ``emit``, together constitute the layer
227concept: A layer is a way to wrap a portion of a compiler pipeline (in this case
228the "opt" phase of an LLVM compiler) whose API is is opaque to ORC in an
229interface that allows ORC to invoke it when needed. The add method takes an
230module in some input program representation (in this case an LLVM IR module) and
231stores it in the target JITDylib, arranging for it to be passed back to the
232Layer's emit method when any symbol defined by that module is requested. Layers
233can compose neatly by calling the 'emit' method of a base layer to complete
234their work. For example, in this tutorial our IRTransformLayer calls through to
235our IRCompileLayer to compile the transformed IR, and our IRCompileLayer in turn
236calls our ObjectLayer to link the object file produced by our compiler.
237
238
239So far we have learned how to optimize and compile our LLVM IR, but we have not
240focused on when compilation happens. Our current REPL is eager: Each function
241definition is optimized and compiled as soon as it is referenced by any other
242code, regardless of whether it is ever called at runtime. In the next chapter we
243will introduce fully lazy compilation, in which functions are not compiled until
244they are first called at run-time. At this point the trade-offs get much more
Lang Hamesc499d2a2016-06-06 03:28:12 +0000245interesting: the lazier we are, the quicker we can start executing the first
Lang Hames575515f2018-11-13 01:25:34 +0000246function, but the more often we will have to pause to compile newly encountered
247functions. If we only code-gen lazily, but optimize eagerly, we will have a
248longer startup time (as everything is optimized) but relatively short pauses as
249each function just passes through code-gen. If we both optimize and code-gen
250lazily we can start executing the first function more quickly, but we will have
251longer pauses as each function has to be both optimized and code-gen'd when it
252is first executed. Things become even more interesting if we consider
253interproceedural optimizations like inlining, which must be performed eagerly.
254These are complex trade-offs, and there is no one-size-fits all solution to
255them, but by providing composable layers we leave the decisions to the person
256implementing the JIT, and make it easy for them to experiment with different
257configurations.
Lang Hamesc499d2a2016-06-06 03:28:12 +0000258
259`Next: Adding Per-function Lazy Compilation <BuildingAJIT3.html>`_
Lang Hamesbe84d2be2016-05-26 00:38:04 +0000260
261Full Code Listing
262=================
263
264Here is the complete code listing for our running example with an
265IRTransformLayer added to enable optimization. To build this example, use:
266
267.. code-block:: bash
268
269 # Compile
Don Hinton4b93d232017-09-17 00:24:43 +0000270 clang++ -g toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core orcjit native` -O3 -o toy
Lang Hamesbe84d2be2016-05-26 00:38:04 +0000271 # Run
272 ./toy
273
274Here is the code:
275
276.. literalinclude:: ../../examples/Kaleidoscope/BuildingAJIT/Chapter2/KaleidoscopeJIT.h
277 :language: c++