blob: 3e8d3fbef5a9f1e703d4b7f756def50850f35981 [file] [log] [blame]
Lang Hames42c9b592016-05-26 21:17:06 +00001=============================================
2Building a JIT: Per-function Lazy Compilation
3=============================================
4
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 3 Introduction
13======================
14
15Welcome to Chapter 3 of the "Building an ORC-based JIT in LLVM" tutorial. This
16chapter discusses lazy JITing and shows you how to enable it by adding an ORC
17CompileOnDemand layer the JIT from `Chapter 2 <BuildingAJIT2.html>`_.
18
Lang Hames7cd3ac72016-07-15 01:39:49 +000019Lazy Compilation
20================
21
22When we add a module to the KaleidoscopeJIT class described in Chapter 2 it is
23immediately optimized, compiled and linked for us by the IRTransformLayer,
24IRCompileLayer and ObjectLinkingLayer respectively. This scheme, where all the
25work to make a Module executable is done up front, is relatively simple to
26understand its performance characteristics are easy to reason about. However,
27it will lead to very high startup times if the amount of code to be compiled is
28large, and may also do a lot of unnecessary compilation if only a few compiled
29functions are ever called at runtime. A truly "just-in-time" compiler should
30allow us to defer the compilation of any given function until the moment that
31function is first called, improving launch times and eliminating redundant work.
32In fact, the ORC APIs provide us with a layer to lazily compile LLVM IR:
33*CompileOnDemandLayer*.
34
35The CompileOnDemandLayer conforms to the layer interface described in Chapter 2,
36but the addModuleSet method behaves quite differently from the layers we have
37seen so far: rather than doing any work up front, it just constructs a *stub*
38for each function in the module and arranges for the stub to trigger compilation
39of the actual function the first time it is called. Because stub functions are
40very cheap to produce CompileOnDemand's addModuleSet method runs very quickly,
41reducing the time required to launch the first function to be executed, and
42saving us from doing any redundant compilation. By conforming to the layer
43interface, CompileOnDemand can be easily added on top of our existing JIT class.
44We just need a few changes:
45
46.. code-block:: c++
47
48 ...
49 #include "llvm/ExecutionEngine/SectionMemoryManager.h"
50 #include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h"
51 #include "llvm/ExecutionEngine/Orc/CompileUtils.h"
52 ...
53
54 ...
55 class KaleidoscopeJIT {
56 private:
57 std::unique_ptr<TargetMachine> TM;
58 const DataLayout DL;
59 std::unique_ptr<JITCompileCallbackManager> CompileCallbackManager;
60 ObjectLinkingLayer<> ObjectLayer;
61 IRCompileLayer<decltype(ObjectLayer)> CompileLayer;
62
63 typedef std::function<std::unique_ptr<Module>(std::unique_ptr<Module>)>
64 OptimizeFunction;
65
66 IRTransformLayer<decltype(CompileLayer), OptimizeFunction> OptimizeLayer;
67 CompileOnDemandLayer<decltype(OptimizeLayer)> CODLayer;
68
69 public:
70 typedef decltype(CODLayer)::ModuleSetHandleT ModuleHandle;
71
72First we need to include the CompileOnDemandLayer.h header, then add two new
73members: a std::unique_ptr<CompileCallbackManager> and a CompileOnDemandLayer,
74to our class. The CompileCallbackManager is a utility that enables us to
75create re-entry points into the compiler for functions that we want to lazily
76compile. In the next chapter we'll be looking at this class in detail, but for
77now we'll be treating it as an opaque utility: We just need to pass a reference
78to it into our new CompileOnDemandLayer, and the layer will do all the work of
79setting up the callbacks using the callback manager we gave it.
80
81
82 KaleidoscopeJIT()
83 : TM(EngineBuilder().selectTarget()), DL(TM->createDataLayout()),
84 CompileLayer(ObjectLayer, SimpleCompiler(*TM)),
85 OptimizeLayer(CompileLayer,
86 [this](std::unique_ptr<Module> M) {
87 return optimizeModule(std::move(M));
88 }),
89 CompileCallbackManager(
90 orc::createLocalCompileCallbackManager(TM->getTargetTriple(), 0)),
91 CODLayer(OptimizeLayer,
92 [this](Function &F) { return std::set<Function*>({&F}); },
93 *CompileCallbackManager,
94 orc::createLocalIndirectStubsManagerBuilder(
95 TM->getTargetTriple())) {
96 llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr);
97 }
98
99Next we have to update our constructor to initialize the new members. To create
100an appropriate compile callback manager we use the
101createLocalCompileCallbackManager function, which takes a TargetMachine and a
102TargetAddress to call if it receives a request to compile an unknown function.
103In our simple JIT this situation is unlikely to come up, so we'll cheat and
104just pass '0' here. In a production quality JIT you could give the address of a
105function that throws an exception in order to unwind the JIT'd code stack.
106
107Now we can construct our CompileOnDemandLayer. Following the pattern from
108previous layers we start by passing a reference to the next layer down in our
109stack -- the OptimizeLayer. Next we need to supply a 'partitioning function':
110when a not-yet-compiled function is called, the CompileOnDemandLayer will call
111this function to ask us what we would like to compile. At a minimum we need to
112compile the function being called (given by the argument to the partitioning
113function), but we could also request that the CompileOnDemandLayer compile other
114functions that are unconditionally called (or highly likely to be called) from
115the function being called. For KaleidoscopeJIT we'll keep it simple and just
116request compilation of the function that was called. Next we pass a reference to
117our CompileCallbackManager. Finally, we need to supply an "indirect stubs
118manager builder". This is a function that constructs IndirectStubManagers, which
119are in turn used to build the stubs for each module. The CompileOnDemandLayer
120will call the indirect stub manager builder once for each call to addModuleSet,
121and use the resulting indirect stubs manager to create stubs for all functions
122in all modules added. If/when the module set is removed from the JIT the
123indirect stubs manager will be deleted, freeing any memory allocated to the
124stubs. We supply this function by using the
125createLocalIndirectStubsManagerBuilder utility.
126
127 // ...
128 if (auto Sym = CODLayer.findSymbol(Name, false))
129 // ...
130 return CODLayer.addModuleSet(std::move(Ms),
131 make_unique<SectionMemoryManager>(),
132 std::move(Resolver));
133 // ...
134
135 // ...
136 return CODLayer.findSymbol(MangledNameStream.str(), true);
137 // ...
138
139 // ...
140 CODLayer.removeModuleSet(H);
141 // ...
142
143Finally, we need to replace the references to OptimizeLayer in our addModule,
144findSymbol, and removeModule methods. With that, we're up and running.
145
Lang Hames42c9b592016-05-26 21:17:06 +0000146**To be done:**
147
Lang Hames7cd3ac72016-07-15 01:39:49 +0000148** Discuss CompileCallbackManagers and IndirectStubManagers in more detail.**
Lang Hames42c9b592016-05-26 21:17:06 +0000149
150Full Code Listing
151=================
152
153Here is the complete code listing for our running example with a CompileOnDemand
154layer added to enable lazy function-at-a-time compilation. To build this example, use:
155
156.. code-block:: bash
157
158 # Compile
159 clang++ -g toy.cpp `llvm-config --cxxflags --ldflags --system-libs --libs core orc native` -O3 -o toy
160 # Run
161 ./toy
162
163Here is the code:
164
165.. literalinclude:: ../../examples/Kaleidoscope/BuildingAJIT/Chapter3/KaleidoscopeJIT.h
166 :language: c++
167
168`Next: Extreme Laziness -- Using Compile Callbacks to JIT directly from ASTs <BuildingAJIT4.html>`_