MLIR 24.0.0git
JitRunner.cpp
Go to the documentation of this file.
1//===- jit-runner.cpp - MLIR CPU Execution Driver Library -----------------===//
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 is a library that provides a shared implementation for command line
10// utilities that execute an MLIR file on the CPU by translating MLIR to LLVM
11// IR before JIT-compiling and executing the latter.
12//
13// The translation can be customized by providing an MLIR to MLIR
14// transformation.
15//===----------------------------------------------------------------------===//
16
18
23#include "mlir/IR/MLIRContext.h"
24#include "mlir/Parser/Parser.h"
27
28#include "llvm/ADT/STLExtras.h"
29#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
30#include "llvm/ExecutionEngine/Orc/LLJIT.h"
31#include "llvm/IR/IRBuilder.h"
32#include "llvm/IR/LLVMContext.h"
33#include "llvm/IR/LegacyPassNameParser.h"
34#include "llvm/Support/CommandLine.h"
35#include "llvm/Support/Debug.h"
36#include "llvm/Support/FileUtilities.h"
37#include "llvm/Support/LibraryOptionsContext.h"
38#include "llvm/Support/SourceMgr.h"
39#include "llvm/Support/StringSaver.h"
40#include "llvm/Support/ToolOutputFile.h"
41#include <cstdint>
42#include <numeric>
43#include <optional>
44#include <utility>
45
46#define DEBUG_TYPE "jit-runner"
47
48using namespace mlir;
49using llvm::Error;
50
51namespace {
52/// This options struct prevents the need for global static initializers, and
53/// is only initialized if the JITRunner is invoked.
54struct Options {
55 llvm::cl::opt<std::string> inputFilename{llvm::cl::Positional,
56 llvm::cl::desc("<input file>"),
57 llvm::cl::init("-")};
58 llvm::cl::opt<std::string> mainFuncName{
59 "e", llvm::cl::desc("The function to be called"),
60 llvm::cl::value_desc("<function name>"), llvm::cl::init("main")};
61 llvm::cl::opt<std::string> mainFuncType{
62 "entry-point-result",
63 llvm::cl::desc("Textual description of the function type to be called"),
64 llvm::cl::value_desc("f32 | i32 | i64 | void"), llvm::cl::init("f32")};
65
66 llvm::cl::OptionCategory optFlags{"opt-like flags"};
67
68 // CLI variables for -On options.
69 llvm::cl::opt<bool> optO0{"O0",
70 llvm::cl::desc("Run opt passes and codegen at O0"),
71 llvm::cl::cat(optFlags)};
72 llvm::cl::opt<bool> optO1{"O1",
73 llvm::cl::desc("Run opt passes and codegen at O1"),
74 llvm::cl::cat(optFlags)};
75 llvm::cl::opt<bool> optO2{"O2",
76 llvm::cl::desc("Run opt passes and codegen at O2"),
77 llvm::cl::cat(optFlags)};
78 llvm::cl::opt<bool> optO3{"O3",
79 llvm::cl::desc("Run opt passes and codegen at O3"),
80 llvm::cl::cat(optFlags)};
81
82 llvm::cl::list<std::string> mAttrs{
83 "mattr", llvm::cl::MiscFlags::CommaSeparated,
84 llvm::cl::desc("Target specific attributes (-mattr=help for details)"),
85 llvm::cl::value_desc("a1,+a2,-a3,..."), llvm::cl::cat(optFlags)};
86
87 llvm::cl::opt<std::string> mArch{
88 "march",
89 llvm::cl::desc("Architecture to generate code for (see --version)")};
90
91 llvm::cl::OptionCategory clOptionsCategory{"linking options"};
92 llvm::cl::list<std::string> clSharedLibs{
93 "shared-libs", llvm::cl::desc("Libraries to link dynamically"),
94 llvm::cl::MiscFlags::CommaSeparated, llvm::cl::cat(clOptionsCategory)};
95
96 /// CLI variables for debugging.
97 llvm::cl::opt<bool> dumpObjectFile{
98 "dump-object-file",
99 llvm::cl::desc("Dump JITted-compiled object to file specified with "
100 "-object-filename (<input file>.o by default).")};
101
102 llvm::cl::opt<std::string> objectFilename{
103 "object-filename",
104 llvm::cl::desc("Dump JITted-compiled object to file <input file>.o")};
105
106 llvm::cl::opt<bool> hostSupportsJit{"host-supports-jit",
107 llvm::cl::desc("Report host JIT support"),
108 llvm::cl::Hidden};
109
110 llvm::cl::opt<bool> noImplicitModule{
111 "no-implicit-module",
112 llvm::cl::desc(
113 "Disable implicit addition of a top-level module op during parsing"),
114 llvm::cl::init(false)};
115};
116
117struct CompileAndExecuteConfig {
118 /// Library options passed to ExecutionEngine.
119 const llvm::cl::LibraryOptionsContext *libraryOptions = nullptr;
120
121 /// LLVM module transformer that is passed to ExecutionEngine.
122 std::function<llvm::Error(llvm::Module *)> transformer;
123
124 /// A custom function that is passed to ExecutionEngine. It processes MLIR
125 /// module and creates LLVM IR module.
127 llvm::LLVMContext &)>
128 llvmModuleBuilder;
129
130 /// A custom function that is passed to ExecutinEngine to register symbols at
131 /// runtime.
132 llvm::function_ref<llvm::orc::SymbolMap(llvm::orc::MangleAndInterner)>
133 runtimeSymbolMap;
134};
135
136} // namespace
137
138static OwningOpRef<Operation *> parseMLIRInput(StringRef inputFilename,
139 bool insertImplicitModule,
140 MLIRContext *context) {
141 // Set up the input file.
142 std::string errorMessage;
143 auto file = openInputFile(inputFilename, &errorMessage);
144 if (!file) {
145 llvm::errs() << errorMessage << "\n";
146 return nullptr;
147 }
148
149 auto sourceMgr = std::make_shared<llvm::SourceMgr>();
150 sourceMgr->AddNewSourceBuffer(std::move(file), SMLoc());
152 parseSourceFileForTool(sourceMgr, context, insertImplicitModule);
153 if (!module)
154 return nullptr;
155 if (!module.get()->hasTrait<OpTrait::SymbolTable>()) {
156 llvm::errs() << "Error: top-level op must be a symbol table.\n";
157 return nullptr;
158 }
159 return module;
160}
161
162static inline Error makeStringError(const Twine &message) {
163 return llvm::make_error<llvm::StringError>(message.str(),
164 llvm::inconvertibleErrorCode());
165}
166
167static std::optional<unsigned> getCommandLineOptLevel(Options &options) {
168 std::optional<unsigned> optLevel;
170 options.optO0, options.optO1, options.optO2, options.optO3};
171
172 // Determine if there is an optimization flag present.
173 for (unsigned j = 0; j < 4; ++j) {
174 auto &flag = optFlags[j].get();
175 if (flag) {
176 optLevel = j;
177 break;
178 }
179 }
180 return optLevel;
181}
182
183// JIT-compile the given module and run "entryPoint" with "args" as arguments.
184static Error
185compileAndExecute(Options &options, Operation *module, StringRef entryPoint,
186 CompileAndExecuteConfig config, void **args,
187 std::unique_ptr<llvm::TargetMachine> tm = nullptr) {
188 std::optional<llvm::CodeGenOptLevel> jitCodeGenOptLevel;
189 if (auto clOptLevel = getCommandLineOptLevel(options))
190 jitCodeGenOptLevel = static_cast<llvm::CodeGenOptLevel>(*clOptLevel);
191
192 SmallVector<StringRef, 4> sharedLibs(options.clSharedLibs.begin(),
193 options.clSharedLibs.end());
194
195 mlir::ExecutionEngineOptions engineOptions;
196 engineOptions.libraryOptions = config.libraryOptions;
197 engineOptions.llvmModuleBuilder = config.llvmModuleBuilder;
198 if (config.transformer)
199 engineOptions.transformer = config.transformer;
200 engineOptions.jitCodeGenOptLevel = jitCodeGenOptLevel;
201 engineOptions.sharedLibPaths = sharedLibs;
202 engineOptions.enableObjectDump = true;
203 auto expectedEngine =
204 mlir::ExecutionEngine::create(module, engineOptions, std::move(tm));
205 if (!expectedEngine)
206 return expectedEngine.takeError();
207
208 auto engine = std::move(*expectedEngine);
209
210 engine->initialize();
211
212 auto expectedFPtr = engine->lookupPacked(entryPoint);
213 if (!expectedFPtr)
214 return expectedFPtr.takeError();
215
216 if (options.dumpObjectFile)
217 engine->dumpToObjectFile(options.objectFilename.empty()
218 ? options.inputFilename + ".o"
219 : options.objectFilename);
220
221 void (*fptr)(void **) = *expectedFPtr;
222 (*fptr)(args);
223
224 return Error::success();
225}
226
228 Options &options, Operation *module, StringRef entryPoint,
229 CompileAndExecuteConfig config, std::unique_ptr<llvm::TargetMachine> tm) {
230 auto mainFunction = dyn_cast_or_null<LLVM::LLVMFuncOp>(
231 SymbolTable::lookupSymbolIn(module, entryPoint));
232 if (!mainFunction || mainFunction.isExternal())
233 return makeStringError("entry point not found");
234
235 if (cast<LLVM::LLVMFunctionType>(mainFunction.getFunctionType())
236 .getNumParams() != 0)
237 return makeStringError(
238 "JIT can't invoke a main function expecting arguments");
239
240 auto resultType = dyn_cast<LLVM::LLVMVoidType>(
241 mainFunction.getFunctionType().getReturnType());
242 if (!resultType)
243 return makeStringError("expected void function");
244
245 void *empty = nullptr;
246 return compileAndExecute(options, module, entryPoint, std::move(config),
247 &empty, std::move(tm));
248}
249
250template <typename Type>
251Error checkCompatibleReturnType(LLVM::LLVMFuncOp mainFunction);
252template <>
253Error checkCompatibleReturnType<int32_t>(LLVM::LLVMFuncOp mainFunction) {
254 auto resultType = dyn_cast<IntegerType>(
255 cast<LLVM::LLVMFunctionType>(mainFunction.getFunctionType())
256 .getReturnType());
257 if (!resultType || resultType.getWidth() != 32)
258 return makeStringError("only single i32 function result supported");
259 return Error::success();
260}
261template <>
262Error checkCompatibleReturnType<int64_t>(LLVM::LLVMFuncOp mainFunction) {
263 auto resultType = dyn_cast<IntegerType>(
264 cast<LLVM::LLVMFunctionType>(mainFunction.getFunctionType())
265 .getReturnType());
266 if (!resultType || resultType.getWidth() != 64)
267 return makeStringError("only single i64 function result supported");
268 return Error::success();
269}
270template <>
271Error checkCompatibleReturnType<float>(LLVM::LLVMFuncOp mainFunction) {
272 if (!isa<Float32Type>(
273 cast<LLVM::LLVMFunctionType>(mainFunction.getFunctionType())
274 .getReturnType()))
275 return makeStringError("only single f32 function result supported");
276 return Error::success();
277}
278template <typename Type>
280 Options &options, Operation *module, StringRef entryPoint,
281 CompileAndExecuteConfig config, std::unique_ptr<llvm::TargetMachine> tm) {
282 auto mainFunction = dyn_cast_or_null<LLVM::LLVMFuncOp>(
283 SymbolTable::lookupSymbolIn(module, entryPoint));
284 if (!mainFunction || mainFunction.isExternal())
285 return makeStringError("entry point not found");
286
287 if (cast<LLVM::LLVMFunctionType>(mainFunction.getFunctionType())
288 .getNumParams() != 0)
289 return makeStringError(
290 "JIT can't invoke a main function expecting arguments");
291
292 if (Error error = checkCompatibleReturnType<Type>(mainFunction))
293 return error;
294
295 Type res;
296 struct {
297 void *data;
298 } data;
299 data.data = &res;
300 if (auto error =
301 compileAndExecute(options, module, entryPoint, std::move(config),
302 (void **)&data, std::move(tm)))
303 return error;
304
305 // Intentional printing of the output so we can test.
306 llvm::outs() << res << '\n';
307
308 return Error::success();
309}
310
311/// Entry point for all CPU runners. Expects the common argc/argv arguments for
312/// standard C++ main functions.
313int mlir::JitRunnerMain(int argc, char **argv, const DialectRegistry &registry,
314 JitRunnerConfig config) {
315 llvm::ExitOnError exitOnErr;
316
317 // Create the options struct containing the command line options for the
318 // runner. This must come before the command line options are parsed.
319 Options options;
320 llvm::cl::LibraryOptionsContext libraryOptions;
321 llvm::cl::parseCommandLineOptions(libraryOptions, ArrayRef(argv, argc),
322 "MLIR CPU execution driver\n");
323
324 if (options.hostSupportsJit) {
325 auto j = llvm::orc::LLJITBuilder().create();
326 if (j)
327 llvm::outs() << "true\n";
328 else {
329 llvm::outs() << "false\n";
330 exitOnErr(j.takeError());
331 }
332 return 0;
333 }
334
335 std::optional<unsigned> optLevel = getCommandLineOptLevel(options);
337 options.optO0, options.optO1, options.optO2, options.optO3};
338
339 MLIRContext context(registry);
340
341 auto m = parseMLIRInput(options.inputFilename, !options.noImplicitModule,
342 &context);
343 if (!m) {
344 llvm::errs() << "could not parse the input IR\n";
345 return 1;
346 }
347
348 JitRunnerOptions runnerOptions{options.mainFuncName, options.mainFuncType};
349 if (config.mlirTransformer)
350 if (failed(config.mlirTransformer(m.get(), runnerOptions)))
351 return EXIT_FAILURE;
352
353 auto tmBuilderOrError = llvm::orc::JITTargetMachineBuilder::detectHost();
354 if (!tmBuilderOrError) {
355 llvm::errs() << "Failed to create a JITTargetMachineBuilder for the host\n";
356 return EXIT_FAILURE;
357 }
358
359 // Configure TargetMachine builder based on the command line options
360 llvm::SubtargetFeatures features;
361 if (!options.mAttrs.empty()) {
362 for (StringRef attr : options.mAttrs)
363 features.AddFeature(attr);
364 tmBuilderOrError->addFeatures(features.getFeatures());
365 }
366
367 if (!options.mArch.empty()) {
368 tmBuilderOrError->getTargetTriple().setArchName(options.mArch);
369 }
370
371 // Build TargetMachine
372 tmBuilderOrError->getOptions().MCOptions.LibraryOptions = &libraryOptions;
373 auto tmOrError = tmBuilderOrError->createTargetMachine();
374
375 if (!tmOrError) {
376 llvm::errs() << "Failed to create a TargetMachine for the host\n";
377 exitOnErr(tmOrError.takeError());
378 }
379
380 LLVM_DEBUG({
381 llvm::dbgs() << " JITTargetMachineBuilder is "
382 << llvm::orc::JITTargetMachineBuilderPrinter(*tmBuilderOrError,
383 "\n");
384 });
385
386 CompileAndExecuteConfig compileAndExecuteConfig;
387 compileAndExecuteConfig.libraryOptions = &libraryOptions;
388 if (optLevel) {
389 compileAndExecuteConfig.transformer = mlir::makeOptimizingTransformer(
390 *optLevel, /*sizeLevel=*/0, /*targetMachine=*/tmOrError->get());
391 }
392 compileAndExecuteConfig.llvmModuleBuilder = config.llvmModuleBuilder;
393 compileAndExecuteConfig.runtimeSymbolMap = config.runtimesymbolMap;
394
395 // Get the function used to compile and execute the module.
396 using CompileAndExecuteFnT =
397 Error (*)(Options &, Operation *, StringRef, CompileAndExecuteConfig,
398 std::unique_ptr<llvm::TargetMachine> tm);
399 auto compileAndExecuteFn =
400 StringSwitch<CompileAndExecuteFnT>(options.mainFuncType.getValue())
404 .Case("void", compileAndExecuteVoidFunction)
405 .Default(nullptr);
406
407 Error error = compileAndExecuteFn
408 ? compileAndExecuteFn(
409 options, m.get(), options.mainFuncName.getValue(),
410 compileAndExecuteConfig, std::move(tmOrError.get()))
411 : makeStringError("unsupported function type");
412
413 int exitCode = EXIT_SUCCESS;
414 llvm::handleAllErrors(std::move(error),
415 [&exitCode](const llvm::ErrorInfoBase &info) {
416 llvm::errs() << "Error: ";
417 info.log(llvm::errs());
418 llvm::errs() << '\n';
419 exitCode = EXIT_FAILURE;
420 });
421
422 return exitCode;
423}
Error checkCompatibleReturnType< int64_t >(LLVM::LLVMFuncOp mainFunction)
Error checkCompatibleReturnType(LLVM::LLVMFuncOp mainFunction)
static Error compileAndExecute(Options &options, Operation *module, StringRef entryPoint, CompileAndExecuteConfig config, void **args, std::unique_ptr< llvm::TargetMachine > tm=nullptr)
static OwningOpRef< Operation * > parseMLIRInput(StringRef inputFilename, bool insertImplicitModule, MLIRContext *context)
static Error compileAndExecuteVoidFunction(Options &options, Operation *module, StringRef entryPoint, CompileAndExecuteConfig config, std::unique_ptr< llvm::TargetMachine > tm)
Error checkCompatibleReturnType< float >(LLVM::LLVMFuncOp mainFunction)
static Error compileAndExecuteSingleReturnFunction(Options &options, Operation *module, StringRef entryPoint, CompileAndExecuteConfig config, std::unique_ptr< llvm::TargetMachine > tm)
static Error makeStringError(const Twine &message)
static std::optional< unsigned > getCommandLineOptLevel(Options &options)
Error checkCompatibleReturnType< int32_t >(LLVM::LLVMFuncOp mainFunction)
@ Error
static llvm::ManagedStatic< PassManagerOptions > options
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
static llvm::Expected< std::unique_ptr< ExecutionEngine > > create(Operation *op, const ExecutionEngineOptions &options={}, std::unique_ptr< llvm::TargetMachine > tm=nullptr)
Creates an execution engine for the given MLIR IR.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:64
A trait used to provide symbol table functionalities to a region operation.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
This class acts as an owning reference to an op, and will automatically destroy the held op on destru...
Definition OwningOpRef.h:29
OpTy get() const
Allow accessing the internal op.
Definition OwningOpRef.h:51
static Operation * lookupSymbolIn(Operation *op, StringAttr symbol)
Returns the operation registered with the given symbol name with the regions of 'symbolTableOp'.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
Include the generated interface declarations.
std::function< llvm::Error(llvm::Module *)> makeOptimizingTransformer(unsigned optLevel, unsigned sizeLevel, llvm::TargetMachine *targetMachine)
Create a module transformer function for MLIR ExecutionEngine that runs LLVM IR passes corresponding ...
int JitRunnerMain(int argc, char **argv, const DialectRegistry &registry, JitRunnerConfig config={})
Entry point for all CPU runners.
std::unique_ptr< llvm::MemoryBuffer > openInputFile(llvm::StringRef inputFilename, std::string *errorMessage=nullptr)
Open the file specified by its name for reading.
OwningOpRef< Operation * > parseSourceFileForTool(const std::shared_ptr< llvm::SourceMgr > &sourceMgr, const ParserConfig &config, bool insertImplicitModule)
This parses the file specified by the indicated SourceMgr.
llvm::StringSwitch< T, R > StringSwitch
Definition LLVM.h:144
llvm::function_ref< std::unique_ptr< llvm::Module >(Operation *, llvm::LLVMContext &)> llvmModuleBuilder
If llvmModuleBuilder is provided, it will be used to create an LLVM module from the given MLIR IR.
std::optional< llvm::CodeGenOptLevel > jitCodeGenOptLevel
jitCodeGenOptLevel, when provided, is used as the optimization level for target code generation.
ArrayRef< StringRef > sharedLibPaths
If sharedLibPaths are provided, the underlying JIT-compilation will open and link the shared librarie...
bool enableObjectDump
If enableObjectCache is set, the JIT compiler will create one to store the object generated for the g...
const llvm::cl::LibraryOptionsContext * libraryOptions
Library options for the LLVMContext and the default TargetMachine.
llvm::function_ref< llvm::Error(llvm::Module *)> transformer
If transformer is provided, it will be called on the LLVM module during JIT-compilation and can be us...
Configuration to override functionality of the JitRunner.
Definition JitRunner.h:48
llvm::function_ref< llvm::LogicalResult(mlir::Operation *, JitRunnerOptions &options)> mlirTransformer
MLIR transformer applied after parsing the input into MLIR IR and before passing the MLIR IR to the E...
Definition JitRunner.h:53
llvm::function_ref< llvm::orc::SymbolMap(llvm::orc::MangleAndInterner)> runtimesymbolMap
A callback to register symbols with ExecutionEngine at runtime.
Definition JitRunner.h:63
llvm::function_ref< std::unique_ptr< llvm::Module >(Operation *, llvm::LLVMContext &)> llvmModuleBuilder
A custom function that is passed to ExecutionEngine.
Definition JitRunner.h:59
JitRunner command line options used by JitRunnerConfig methods.
Definition JitRunner.h:40
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.