Skip to content

Commit

Permalink
[BuildingAJIT] Update the Ch1 KaleidoscopeJIT class to expose errors …
Browse files Browse the repository at this point in the history
…to clients.

Returning the error to clients provides an opportunity to introduce readers to
the Expected and Error APIs and makes the tutorial more useful as a starting
point for a real JIT class, while only slightly complicating the code.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@344720 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
lhames committed Oct 18, 2018
1 parent 56c3851 commit 009a762
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 116 deletions.
164 changes: 84 additions & 80 deletions docs/tutorial/BuildingAJIT1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Chapter 1 Introduction
======================

**Warning: This tutorial is currently being updated to account for ORC API
changes. Only Chapter 1 is up-to-date.**
changes. Only Chapters 1 and 2 are up-to-date.**

**Example code from Chapters 2 to 4 will compile and run, but has not been
**Example code from Chapters 3 to 5 will compile and run, but has not been
updated**

Welcome to Chapter 1 of the "Building an ORC-based JIT in LLVM" tutorial. This
Expand Down Expand Up @@ -65,9 +65,9 @@ rather than compiling whole programs to disk ahead of time as a traditional
compiler does. To support that aim our initial, bare-bones JIT API will have
just two functions:

1. void addModule(std::unique_ptr<Module> M) -- Make the given IR module
1. ``Error addModule(std::unique_ptr<Module> M)``: Make the given IR module
available for execution.
2. Expected<JITSymbol> lookup() -- Search for pointers to
2. ``Expected<JITEvaluatedSymbol> lookup()``: Search for pointers to
symbols (functions or variables) that have been added to the JIT.

A basic use-case for this API, executing the 'main' function from a module,
Expand Down Expand Up @@ -127,94 +127,95 @@ usual include guards and #includes [2]_, we get to the definition of our class:

class KaleidoscopeJIT {
private:

ExecutionSession ES;
RTDyldObjectLinkingLayer ObjectLayer{ES, getMemoryMgr};
IRCompileLayer CompileLayer{ES, ObjectLayer,
ConcurrentIRCompiler(getJTMB())};
DataLayout DL{cantFail(getJTMB().getDefaultDataLayoutForTarget())};
MangleAndInterner Mangle{ES, DL};
ThreadSafeContext Ctx{llvm::make_unique<LLVMContext>()};

static JITTargetMachineBuilder getJTMB() {
return cantFail(JITTargetMachineBuilder::detectHost());
}
RTDyldObjectLinkingLayer ObjectLayer;
IRCompileLayer CompileLayer;

DataLayout DL;
MangleAndInterner Mangle;
ThreadSafeContext Ctx;

static std::unique_ptr<SectionMemoryManager> getMemoryMgr(VModuleKey) {
return llvm::make_unique<SectionMemoryManager>();
public:
KaleidoscopeJIT(JITTargetMachineBuilder JTMB, DataLayout DL)
: ObjectLayer(ES,
[]() { return llvm::make_unique<SectionMemoryManager>(); }),
CompileLayer(ES, ObjectLayer, ConcurrentIRCompiler(std::move(JTMB))),
DL(std::move(DL)), Mangle(ES, this->DL),
Ctx(llvm::make_unique<LLVMContext>()) {
ES.getMainJITDylib().setGenerator(
cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(DL)));
}

We begin with the ExecutionSession member, ``ES``, which provides context for
our running JIT'd code. It holds the string pool for symbol names, the global
mutex that guards the critical sections of JIT operations, error logging
facilities, and other utilities. For basic use cases such as this, a default
constructed ExecutionSession is all we will need. We will investigate more
advanced uses of ExecutionSession in later chapters. Following our
ExecutionSession we have two ORC *layers*: an RTDyldObjectLinkingLayer and an
IRCompileLayer. We will be talking more about layers in the next chapter, but
for now you can think of them as analogous to LLVM Passes: they wrap up useful
JIT utilities behind an easy to compose interface. The first layer, ObjectLayer,
is the foundation of our JIT: it takes in-memory object files produced by a
compiler and links them on the fly to make them executable. This
JIT-on-top-of-a-linker design was introduced in MCJIT, however the linker was
hidden inside the MCJIT class. In ORC we expose the linker so that clients can
access and configure it directly if they need to. In this tutorial our
ObjectLayer will just be used to support the next layer in our stack: the
CompileLayer, which will be responsible for taking LLVM IR, compiling it, and
passing the resulting in-memory object files down to the object linking layer
below. Our ObjectLayer is constructed with a reference to the ExecutionSession
and the getMemoryMgr utility function, which it uses to generate a new memory
manager for each object file as it is added. Next up is our CompileLayer, which
is initialized with a reference to the ExecutionSession, a reference to the
ObjectLayer (where it will send the objects produced by the compiler), and an IR
compiler instance. In this case we are using the ConcurrentIRCompiler class
which is constructed with a JITTargetMachineBuilder and can be called to compile
IR concurrently from several threads (though in this chapter we will only use
one).

Following the ExecutionSession and layers we have three supporting member
variables. The DataLayout, ``DL``; and MangleAndInterner, ``Mangle`` members are
used to support portable lookups based on IR symbol names (more on that when we
get to our ``lookup`` function below), and the ThreadSafeContext member,
``Ctx``, manages an LLVMContext that can be used while building IR Modules for
the JIT.

After that, we have two static utility functions. The ``getJTMB()`` function
returns a JITTargetMachineBuilder, which is a factory for building LLVM
TargetMachine instances that are used by the compiler. In this first tutorial we
will only need one (implicitly created) TargetMachine, but in future tutorials
that enable concurrent compilation we will need one per thread. This is why we
use a target machine builder, rather than a single TargetMachine. (note: Older
LLVM JIT APIs that did not support concurrent compilation were constructed with
a single TargetMachines). The ``getMemoryMgr()`` function constructs instances
of RuntimeDyld::MemoryManager, and is used by the linking layer to generate a
new memory manager for each object file.
Our class begins with six member variables: An ExecutionSession member, ``ES``,
which provides context for our running JIT'd code (including the string pool,
global mutex, and error reporting facilities); An RTDyldObjectLinkingLayer,
``ObjectLayer``, that can be used to add object files to our JIT (though we will
not use it directly); An IRCompileLayer, ``CompileLayer``, that can be used to
add LLVM Modules to our JIT (and which builds on the ObjectLayer), A DataLayout
and MangleAndInterner, ``DL`` and ``Mangle``, that will be used for symbol mangling
(more on that later); and finally an LLVMContext that clients will use when
building IR files for the JIT.

Next up we have our class constructor, which takes a `JITTargetMachineBuilder``
that will be used by our IRCompiler, and a ``DataLayout`` that we will use to
initialize our DL member. The constructor begins by initializing our
ObjectLayer. The ObjectLayer requires a reference to the ExecutionSession, and
a function object that will build a JIT memory manager for each module that is
added (a JIT memory manager manages memory allocations, memory permissions, and
registration of exception handlers for JIT'd code). For this we use a lambda
that returns a SectionMemoryManager, an off-the-shelf utility that provides all
the basic memory management functionality required for this chapter. Next we
initialize our CompileLayer. The CompileLayer needs three things: (1) A
reference to the ExecutionSession, (2) A reference to our object layer, and (3)
a compiler instance to use to perform the actual compilation from IR to object
files. We use the off-the-shelf ConcurrentIRCompiler utility as our compiler,
which we construct using this constructor's JITTargetMachineBuilder argument.
The ConcurrentIRCompiler utility will use the JITTargetMachineBuilder to build
llvm TargetMachines (which are not thread safe) as needed for compiles. After
this, we initialize our supporting members: ``DL``, ``Mangler`` and ``Ctx`` with
the input DataLayout, the ExecutionSession and DL member, and a new default
constucted LLVMContext respectively. Now that our members have been initialized,
so the one thing that remains to do is to tweak the configuration of the
*JITDylib* that we will store our code in. We want to modify this dylib to
contain not only the symbols that we add to it, but also the symbols from our
REPL process as well. We do this by attaching a
``DynamicLibrarySearchGenerator`` instance using the
``DynamicLibrarySearchGenerator::GetForCurrentProcess`` method.


.. code-block:: c++

public:
static Expected<std::unique_ptr<KaleidoscopeJIT>> Create() {
auto JTMB = JITTargetMachineBuilder::detectHost();

KaleidoscopeJIT() {
ES.getMainJITDylib().setGenerator(
cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(DL)));
}
if (!JTMB)
return JTMB.takeError();

const DataLayout &getDataLayout() const { return DL; }
auto DL = JTMB->getDefaultDataLayoutForTarget();
if (!DL)
return DL.takeError();

LLVMContext &getContext() { return *Ctx.getContext(); }
return llvm::make_unique<KaleidoscopeJIT>(std::move(*JTMB), std::move(*DL));
}
Next up we have our class constructor. Our members have already been
initialized, so the one thing that remains to do is to tweak the configuration
of the *JITDylib* that we will store our code in. We want to modify this dylib
to contain not only the symbols that we add to it, but also the symbols from
our REPL process as well. We do this by attaching a
``DynamicLibrarySearchGenerator`` instance using the
``DynamicLibrarySearchGenerator::GetForCurrentProcess`` method.
const DataLayout &getDataLayout() const { return DL; }

LLVMContext &getContext() { return *Ctx.getContext(); }
Following the constructor we have the ``getDataLayout()`` and ``getContext()``
methods. These are used to make data structures created and managed by the JIT
(especially the LLVMContext) available to the REPL code that will build our
IR modules.
Next we have a named constructor, ``Create``, which will build a KaleidoscopeJIT
instance that is configured to generate code for our host process. It does this
by first generating a JITTargetMachineBuilder instance using that clases's
detectHost method and then using that instance to generate a datalayout for
the target process. Each of these operations can fail, so each returns its
result wrapped in an Expected value [3]_ that we must check for error before
continuing. If both operations succeed we can unwrap their results (using the
dereference operator) and pass them into KaleidoscopeJIT's constructor on the
last line of the function.

Following the named constructor we have the ``getDataLayout()`` and
``getContext()`` methods. These are used to make data structures created and
managed by the JIT (especially the LLVMContext) available to the REPL code that
will build our IR modules.

.. code-block:: c++

Expand Down Expand Up @@ -317,3 +318,6 @@ Here is the code:
+-----------------------------+-----------------------------------------------+
| LLVMContext.h | Provides the LLVMContext class. |
+-----------------------------+-----------------------------------------------+
.. [3] See the ErrorHandling section in the LLVM Programmer's Manual
(http://llvm.org/docs/ProgrammersManual.html#error-handling)
48 changes: 28 additions & 20 deletions examples/Kaleidoscope/BuildingAJIT/Chapter1/KaleidoscopeJIT.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,45 @@ namespace orc {

class KaleidoscopeJIT {
private:

ExecutionSession ES;
RTDyldObjectLinkingLayer ObjectLayer{ES, getMemoryMgr};
IRCompileLayer CompileLayer{ES, ObjectLayer,
ConcurrentIRCompiler(getJTMB())};
DataLayout DL{cantFail(getJTMB().getDefaultDataLayoutForTarget())};
MangleAndInterner Mangle{ES, DL};
ThreadSafeContext Ctx{llvm::make_unique<LLVMContext>()};

static JITTargetMachineBuilder getJTMB() {
return cantFail(JITTargetMachineBuilder::detectHost());
}
RTDyldObjectLinkingLayer ObjectLayer;
IRCompileLayer CompileLayer;

static std::unique_ptr<SectionMemoryManager> getMemoryMgr() {
return llvm::make_unique<SectionMemoryManager>();
}
DataLayout DL;
MangleAndInterner Mangle;
ThreadSafeContext Ctx;

public:

KaleidoscopeJIT() {
KaleidoscopeJIT(JITTargetMachineBuilder JTMB, DataLayout DL)
: ObjectLayer(ES,
[]() { return llvm::make_unique<SectionMemoryManager>(); }),
CompileLayer(ES, ObjectLayer, ConcurrentIRCompiler(std::move(JTMB))),
DL(std::move(DL)), Mangle(ES, this->DL),
Ctx(llvm::make_unique<LLVMContext>()) {
ES.getMainJITDylib().setGenerator(
cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(DL)));
cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(DL)));
}

static Expected<std::unique_ptr<KaleidoscopeJIT>> Create() {
auto JTMB = JITTargetMachineBuilder::detectHost();

if (!JTMB)
return JTMB.takeError();

auto DL = JTMB->getDefaultDataLayoutForTarget();
if (!DL)
return DL.takeError();

return llvm::make_unique<KaleidoscopeJIT>(std::move(*JTMB), std::move(*DL));
}

const DataLayout &getDataLayout() const { return DL; }

LLVMContext &getContext() { return *Ctx.getContext(); }

void addModule(std::unique_ptr<Module> M) {
cantFail(CompileLayer.add(ES.getMainJITDylib(),
ThreadSafeModule(std::move(M), Ctx)));
Error addModule(std::unique_ptr<Module> M) {
return CompileLayer.add(ES.getMainJITDylib(),
ThreadSafeModule(std::move(M), Ctx));
}

Expected<JITEvaluatedSymbol> lookup(StringRef Name) {
Expand Down
26 changes: 10 additions & 16 deletions examples/Kaleidoscope/BuildingAJIT/Chapter1/toy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ static std::unique_ptr<IRBuilder<>> Builder;
static std::unique_ptr<Module> TheModule;
static std::map<std::string, AllocaInst *> NamedValues;
static std::map<std::string, std::unique_ptr<PrototypeAST>> FunctionProtos;
static ExitOnError ExitOnErr;

Value *LogErrorV(const char *Str) {
LogError(Str);
Expand Down Expand Up @@ -1116,7 +1117,7 @@ static void HandleDefinition() {
fprintf(stderr, "Read function definition:");
FnIR->print(errs());
fprintf(stderr, "\n");
TheJIT->addModule(std::move(TheModule));
ExitOnErr(TheJIT->addModule(std::move(TheModule)));
InitializeModule();
}
} else {
Expand Down Expand Up @@ -1151,23 +1152,16 @@ static void HandleTopLevelExpression() {
if (FnAST->codegen()) {
// JIT the module containing the anonymous expression, keeping a handle so
// we can free it later.
TheJIT->addModule(std::move(TheModule));
ExitOnErr(TheJIT->addModule(std::move(TheModule)));
InitializeModule();

// Get the anonymous expression's JITSymbol.
auto Sym = TheJIT->lookup(("__anon_expr" + Twine(ExprCount)).str());

if (Sym) {
// If the lookup succeeded, cast the symbol's address to a function
// pointer then call it.
auto *FP = (double (*)())(intptr_t)Sym->getAddress();
assert(FP && "Failed to codegen function");
fprintf(stderr, "Evaluated to %f\n", FP());
} else {
// Otherwise log the reason the symbol lookup failed.
logAllUnhandledErrors(Sym.takeError(), errs(),
"Could not evaluate: ");
}
auto Sym =
ExitOnErr(TheJIT->lookup(("__anon_expr" + Twine(ExprCount)).str()));

auto *FP = (double (*)())(intptr_t)Sym.getAddress();
assert(FP && "Failed to codegen function");
fprintf(stderr, "Evaluated to %f\n", FP());
}
} else {
// Skip token for error recovery.
Expand Down Expand Up @@ -1235,7 +1229,7 @@ int main() {
fprintf(stderr, "ready> ");
getNextToken();

TheJIT = llvm::make_unique<KaleidoscopeJIT>();
TheJIT = ExitOnErr(KaleidoscopeJIT::Create());
TheContext = &TheJIT->getContext();

InitializeModule();
Expand Down

0 comments on commit 009a762

Please sign in to comment.