[BuildingAJIT] Update the Ch1 KaleidoscopeJIT class to expose errors to clients.
authorLang Hames <lhames@gmail.com>
Thu, 18 Oct 2018 00:51:38 +0000 (00:51 +0000)
committerLang Hames <lhames@gmail.com>
Thu, 18 Oct 2018 00:51:38 +0000 (00:51 +0000)
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.

llvm-svn: 344720

llvm/docs/tutorial/BuildingAJIT1.rst
llvm/examples/Kaleidoscope/BuildingAJIT/Chapter1/KaleidoscopeJIT.h
llvm/examples/Kaleidoscope/BuildingAJIT/Chapter1/toy.cpp

index 7960ffe..fcb755b 100644 (file)
@@ -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
@@ -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,
@@ -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++
 
@@ -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)
\ No newline at end of file
index d9e320f..1df5aff 100644 (file)
@@ -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) {
index 1d0730f..5a66b36 100644 (file)
@@ -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);
@@ -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 {
@@ -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.
@@ -1235,7 +1229,7 @@ int main() {
   fprintf(stderr, "ready> ");
   getNextToken();
 
-  TheJIT = llvm::make_unique<KaleidoscopeJIT>();
+  TheJIT = ExitOnErr(KaleidoscopeJIT::Create());
   TheContext = &TheJIT->getContext();
 
   InitializeModule();