1 //===- ExecutionEngine.h - MLIR Execution engine and utils -----*- C++ -*--===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file provides a JIT-backed execution engine for MLIR modules.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #ifndef MLIR_EXECUTIONENGINE_EXECUTIONENGINE_H_
14 #define MLIR_EXECUTIONENGINE_EXECUTIONENGINE_H_
15 
16 #include "mlir/Support/LLVM.h"
17 #include "llvm/ExecutionEngine/ObjectCache.h"
18 #include "llvm/ExecutionEngine/Orc/LLJIT.h"
19 #include "llvm/ExecutionEngine/SectionMemoryManager.h"
20 #include "llvm/IR/LLVMContext.h"
21 #include "llvm/Support/Error.h"
22 
23 #include <functional>
24 #include <memory>
25 
26 namespace llvm {
27 template <typename T>
28 class Expected;
29 class Module;
30 class ExecutionEngine;
31 class JITEventListener;
32 class MemoryBuffer;
33 } // namespace llvm
34 
35 namespace mlir {
36 
37 class ModuleOp;
38 
39 /// A simple object cache following Lang's LLJITWithObjectCache example.
40 class SimpleObjectCache : public llvm::ObjectCache {
41 public:
42   void notifyObjectCompiled(const llvm::Module *m,
43                             llvm::MemoryBufferRef objBuffer) override;
44   std::unique_ptr<llvm::MemoryBuffer> getObject(const llvm::Module *m) override;
45 
46   /// Dump cached object to output file `filename`.
47   void dumpToObjectFile(StringRef filename);
48 
49 private:
50   llvm::StringMap<std::unique_ptr<llvm::MemoryBuffer>> cachedObjects;
51 };
52 
53 struct ExecutionEngineOptions {
54   /// If `llvmModuleBuilder` is provided, it will be used to create LLVM module
55   /// from the given MLIR module. Otherwise, a default `translateModuleToLLVMIR`
56   /// function will be used to translate MLIR module to LLVM IR.
57   llvm::function_ref<std::unique_ptr<llvm::Module>(ModuleOp,
58                                                    llvm::LLVMContext &)>
59       llvmModuleBuilder = nullptr;
60 
61   /// If `transformer` is provided, it will be called on the LLVM module during
62   /// JIT-compilation and can be used, e.g., for reporting or optimization.
63   llvm::function_ref<llvm::Error(llvm::Module *)> transformer = {};
64 
65   /// `jitCodeGenOptLevel`, when provided, is used as the optimization level for
66   /// target code generation.
67   Optional<llvm::CodeGenOpt::Level> jitCodeGenOptLevel = llvm::None;
68 
69   /// If `sharedLibPaths` are provided, the underlying JIT-compilation will
70   /// open and link the shared libraries for symbol resolution.
71   ArrayRef<StringRef> sharedLibPaths = {};
72 
73   /// Specifies an existing `sectionMemoryMapper` to be associated with the
74   /// compiled code. If none is provided, a default memory mapper that directly
75   /// calls into the operating system is used.
76   llvm::SectionMemoryManager::MemoryMapper *sectionMemoryMapper = nullptr;
77 
78   /// If `enableObjectCache` is set, the JIT compiler will create one to store
79   /// the object generated for the given module. The contents of the cache can
80   /// be dumped to a file via the `dumpToObjectfile` method.
81   bool enableObjectCache = false;
82 
83   /// If enable `enableGDBNotificationListener` is set, the JIT compiler will
84   /// notify the llvm's global GDB notification listener.
85   bool enableGDBNotificationListener = true;
86 
87   /// If `enablePerfNotificationListener` is set, the JIT compiler will notify
88   /// the llvm's global Perf notification listener.
89   bool enablePerfNotificationListener = true;
90 };
91 
92 /// JIT-backed execution engine for MLIR modules.  Assumes the module can be
93 /// converted to LLVM IR.  For each function, creates a wrapper function with
94 /// the fixed interface
95 ///
96 ///     void _mlir_funcName(void **)
97 ///
98 /// where the only argument is interpreted as a list of pointers to the actual
99 /// arguments of the function, followed by a pointer to the result.  This allows
100 /// the engine to provide the caller with a generic function pointer that can
101 /// be used to invoke the JIT-compiled function.
102 class ExecutionEngine {
103 public:
104   ExecutionEngine(bool enableObjectCache, bool enableGDBNotificationListener,
105                   bool enablePerfNotificationListener);
106 
107   /// Creates an execution engine for the given module.
108   static llvm::Expected<std::unique_ptr<ExecutionEngine>>
109   create(ModuleOp m, const ExecutionEngineOptions &options = {});
110 
111   /// Looks up a packed-argument function wrapping the function with the given
112   /// name and returns a pointer to it. Propagates errors in case of failure.
113   llvm::Expected<void (*)(void **)> lookupPacked(StringRef name) const;
114 
115   /// Looks up the original function with the given name and returns a
116   /// pointer to it. This is not necesarily a packed function. Propagates
117   /// errors in case of failure.
118   llvm::Expected<void *> lookup(StringRef name) const;
119 
120   /// Invokes the function with the given name passing it the list of opaque
121   /// pointers to the actual arguments.
122   llvm::Error invokePacked(StringRef name,
123                            MutableArrayRef<void *> args = llvm::None);
124 
125   /// Trait that defines how a given type is passed to the JIT code. This
126   /// defaults to passing the address but can be specialized.
127   template <typename T>
128   struct Argument {
packArgument129     static void pack(SmallVectorImpl<void *> &args, T &val) {
130       args.push_back(&val);
131     }
132   };
133 
134   /// Tag to wrap an output parameter when invoking a jitted function.
135   template <typename T>
136   struct Result {
ResultResult137     Result(T &result) : value(result) {}
138     T &value;
139   };
140 
141   /// Helper function to wrap an output operand when using
142   /// ExecutionEngine::invoke.
143   template <typename T>
result(T & t)144   static Result<T> result(T &t) {
145     return Result<T>(t);
146   }
147 
148   // Specialization for output parameter: their address is forwarded directly to
149   // the native code.
150   template <typename T>
151   struct Argument<Result<T>> {
152     static void pack(SmallVectorImpl<void *> &args, Result<T> &result) {
153       args.push_back(&result.value);
154     }
155   };
156 
157   /// Invokes the function with the given name passing it the list of arguments
158   /// by value. Function result can be obtain through output parameter using the
159   /// `Result` wrapper defined above. For example:
160   ///
161   ///     func @foo(%arg0 : i32) -> i32 attributes { llvm.emit_c_interface }
162   ///
163   /// can be invoked:
164   ///
165   ///     int32_t result = 0;
166   ///     llvm::Error error = jit->invoke("foo", 42,
167   ///                                     result(result));
168   template <typename... Args>
169   llvm::Error invoke(StringRef funcName, Args... args) {
170     const std::string adapterName =
171         std::string("_mlir_ciface_") + funcName.str();
172     llvm::SmallVector<void *> argsArray;
173     // Pack every arguments in an array of pointers. Delegate the packing to a
174     // trait so that it can be overridden per argument type.
175     // TODO: replace with a fold expression when migrating to C++17.
176     int dummy[] = {0, ((void)Argument<Args>::pack(argsArray, args), 0)...};
177     (void)dummy;
178     return invokePacked(adapterName, argsArray);
179   }
180 
181   /// Set the target triple on the module. This is implicitly done when creating
182   /// the engine.
183   static bool setupTargetTriple(llvm::Module *llvmModule);
184 
185   /// Dump object code to output file `filename`.
186   void dumpToObjectFile(StringRef filename);
187 
188   /// Register symbols with this ExecutionEngine.
189   void registerSymbols(
190       llvm::function_ref<llvm::orc::SymbolMap(llvm::orc::MangleAndInterner)>
191           symbolMap);
192 
193 private:
194   /// Ordering of llvmContext and jit is important for destruction purposes: the
195   /// jit must be destroyed before the context.
196   llvm::LLVMContext llvmContext;
197 
198   /// Underlying LLJIT.
199   std::unique_ptr<llvm::orc::LLJIT> jit;
200 
201   /// Underlying cache.
202   std::unique_ptr<SimpleObjectCache> cache;
203 
204   /// GDB notification listener.
205   llvm::JITEventListener *gdbListener;
206 
207   /// Perf notification listener.
208   llvm::JITEventListener *perfListener;
209 };
210 
211 } // namespace mlir
212 
213 #endif // MLIR_EXECUTIONENGINE_EXECUTIONENGINE_H_
214