xref: /llvm-project/mlir/docs/Tutorials/Toy/Ch-6.md (revision 5ad919a1d6bbe449210f81e4ae7a5f765eb5a976)
15b4a01d4SMehdi Amini# Chapter 6: Lowering to LLVM and CodeGeneration
25b4a01d4SMehdi Amini
35b4a01d4SMehdi Amini[TOC]
45b4a01d4SMehdi Amini
55b4a01d4SMehdi AminiIn the [previous chapter](Ch-5.md), we introduced the
65b4a01d4SMehdi Amini[dialect conversion](../../DialectConversion.md) framework and partially lowered
75b4a01d4SMehdi Aminimany of the `Toy` operations to affine loop nests for optimization. In this
85b4a01d4SMehdi Aminichapter, we will finally lower to LLVM for code generation.
95b4a01d4SMehdi Amini
101842fd50SJacques Pienaar## Lowering to LLVM
115b4a01d4SMehdi Amini
125b4a01d4SMehdi AminiFor this lowering, we will again use the dialect conversion framework to perform
135b4a01d4SMehdi Aminithe heavy lifting. However, this time, we will be performing a full conversion
145b4a01d4SMehdi Aminito the [LLVM dialect](../../Dialects/LLVM.md). Thankfully, we have already
155b4a01d4SMehdi Aminilowered all but one of the `toy` operations, with the last being `toy.print`.
165b4a01d4SMehdi AminiBefore going over the conversion to LLVM, let's lower the `toy.print` operation.
175b4a01d4SMehdi AminiWe will lower this operation to a non-affine loop nest that invokes `printf` for
185b4a01d4SMehdi Aminieach element. Note that, because the dialect conversion framework supports
19a54f4eaeSMogball[transitive lowering](../../../getting_started/Glossary.md/#transitive-lowering),
20a54f4eaeSMogballwe don't need to directly emit operations in the LLVM dialect. By transitive
21a54f4eaeSMogballlowering, we mean that the conversion framework may apply multiple patterns to
22a54f4eaeSMogballfully legalize an operation. In this example, we are generating a structured
23a54f4eaeSMogballloop nest instead of the branch-form in the LLVM dialect. As long as we then
24a54f4eaeSMogballhave a lowering from the loop operations to LLVM, the lowering will still
25a54f4eaeSMogballsucceed.
265b4a01d4SMehdi Amini
275b4a01d4SMehdi AminiDuring lowering we can get, or build, the declaration for printf as so:
285b4a01d4SMehdi Amini
295b4a01d4SMehdi Amini```c++
305b4a01d4SMehdi Amini/// Return a symbol reference to the printf function, inserting it into the
315b4a01d4SMehdi Amini/// module if necessary.
325b4a01d4SMehdi Aministatic FlatSymbolRefAttr getOrInsertPrintf(PatternRewriter &rewriter,
335b4a01d4SMehdi Amini                                           ModuleOp module,
345b4a01d4SMehdi Amini                                           LLVM::LLVMDialect *llvmDialect) {
355b4a01d4SMehdi Amini  auto *context = module.getContext();
365b4a01d4SMehdi Amini  if (module.lookupSymbol<LLVM::LLVMFuncOp>("printf"))
375b4a01d4SMehdi Amini    return SymbolRefAttr::get("printf", context);
385b4a01d4SMehdi Amini
395b4a01d4SMehdi Amini  // Create a function declaration for printf, the signature is:
405b4a01d4SMehdi Amini  //   * `i32 (i8*, ...)`
41ed205f63Slewuathe  auto llvmI32Ty = IntegerType::get(context, 32);
42c69c9e0fSAlex Zinenko  auto llvmI8PtrTy =
43ed205f63Slewuathe      LLVM::LLVMPointerType::get(IntegerType::get(context, 8));
44c69c9e0fSAlex Zinenko  auto llvmFnType = LLVM::LLVMFunctionType::get(llvmI32Ty, llvmI8PtrTy,
455b4a01d4SMehdi Amini                                                /*isVarArg=*/true);
465b4a01d4SMehdi Amini
475b4a01d4SMehdi Amini  // Insert the printf function into the body of the parent module.
485b4a01d4SMehdi Amini  PatternRewriter::InsertionGuard insertGuard(rewriter);
495b4a01d4SMehdi Amini  rewriter.setInsertionPointToStart(module.getBody());
505b4a01d4SMehdi Amini  rewriter.create<LLVM::LLVMFuncOp>(module.getLoc(), "printf", llvmFnType);
515b4a01d4SMehdi Amini  return SymbolRefAttr::get("printf", context);
525b4a01d4SMehdi Amini}
535b4a01d4SMehdi Amini```
545b4a01d4SMehdi Amini
555b4a01d4SMehdi AminiNow that the lowering for the printf operation has been defined, we can specify
565b4a01d4SMehdi Aminithe components necessary for the lowering. These are largely the same as the
575b4a01d4SMehdi Aminicomponents defined in the [previous chapter](Ch-5.md).
585b4a01d4SMehdi Amini
591842fd50SJacques Pienaar### Conversion Target
605b4a01d4SMehdi Amini
615b4a01d4SMehdi AminiFor this conversion, aside from the top-level module, we will be lowering
625b4a01d4SMehdi Aminieverything to the LLVM dialect.
635b4a01d4SMehdi Amini
645b4a01d4SMehdi Amini```c++
655b4a01d4SMehdi Amini  mlir::ConversionTarget target(getContext());
662f21a579SRiver Riddle  target.addLegalDialect<mlir::LLVMDialect>();
67973ddb7dSMehdi Amini  target.addLegalOp<mlir::ModuleOp>();
685b4a01d4SMehdi Amini```
695b4a01d4SMehdi Amini
701842fd50SJacques Pienaar### Type Converter
715b4a01d4SMehdi Amini
725b4a01d4SMehdi AminiThis lowering will also transform the MemRef types which are currently being
735b4a01d4SMehdi Aminioperated on into a representation in LLVM. To perform this conversion, we use a
745b4a01d4SMehdi AminiTypeConverter as part of the lowering. This converter specifies how one type
755b4a01d4SMehdi Aminimaps to another. This is necessary now that we are performing more complicated
765b4a01d4SMehdi Aminilowerings involving block arguments. Given that we don't have any
775b4a01d4SMehdi AminiToy-dialect-specific types that need to be lowered, the default converter is
785b4a01d4SMehdi Aminienough for our use case.
795b4a01d4SMehdi Amini
805b4a01d4SMehdi Amini```c++
815b4a01d4SMehdi Amini  LLVMTypeConverter typeConverter(&getContext());
825b4a01d4SMehdi Amini```
835b4a01d4SMehdi Amini
841842fd50SJacques Pienaar### Conversion Patterns
855b4a01d4SMehdi Amini
865b4a01d4SMehdi AminiNow that the conversion target has been defined, we need to provide the patterns
875b4a01d4SMehdi Aminiused for lowering. At this point in the compilation process, we have a
88a54f4eaeSMogballcombination of `toy`, `affine`, `arith`, and `std` operations. Luckily, the
89a54f4eaeSMogball`affine`, `arith`, and `std` dialects already provide the set of patterns needed
90a54f4eaeSMogballto transform them into LLVM dialect. These patterns allow for lowering the IR in
91a54f4eaeSMogballmultiple stages by relying on
92a54f4eaeSMogball[transitive lowering](../../../getting_started/Glossary.md/#transitive-lowering).
935b4a01d4SMehdi Amini
945b4a01d4SMehdi Amini```c++
95289ecccaSChris Lattner  mlir::RewritePatternSet patterns(&getContext());
965b4a01d4SMehdi Amini  mlir::populateAffineToStdConversionPatterns(patterns, &getContext());
97ace01605SRiver Riddle  mlir::cf::populateSCFToControlFlowConversionPatterns(patterns, &getContext());
98abc362a1SJakub Kuderski  mlir::arith::populateArithToLLVMConversionPatterns(typeConverter,
994a5ff56bSMogball                                                          patterns);
1005a7b9194SRiver Riddle  mlir::populateFuncToLLVMConversionPatterns(typeConverter, patterns);
101ace01605SRiver Riddle  mlir::cf::populateControlFlowToLLVMConversionPatterns(patterns, &getContext());
1025b4a01d4SMehdi Amini
10345d522d6SMatthias Kramm  // The only remaining operation, to lower from the `toy` dialect, is the
1045b4a01d4SMehdi Amini  // PrintOp.
105dc4e913bSChris Lattner  patterns.add<PrintOpLowering>(&getContext());
1065b4a01d4SMehdi Amini```
1075b4a01d4SMehdi Amini
1081842fd50SJacques Pienaar### Full Lowering
1095b4a01d4SMehdi Amini
1105b4a01d4SMehdi AminiWe want to completely lower to LLVM, so we use a `FullConversion`. This ensures
1115b4a01d4SMehdi Aminithat only legal operations will remain after the conversion.
1125b4a01d4SMehdi Amini
1135b4a01d4SMehdi Amini```c++
114722f909fSRiver Riddle  mlir::ModuleOp module = getOperation();
1158d67d187SRiver Riddle  if (mlir::failed(mlir::applyFullConversion(module, target, patterns)))
1165b4a01d4SMehdi Amini    signalPassFailure();
1175b4a01d4SMehdi Amini```
1185b4a01d4SMehdi Amini
1195b4a01d4SMehdi AminiLooking back at our current working example:
1205b4a01d4SMehdi Amini
1215b4a01d4SMehdi Amini```mlir
122ee2c6cd9SRiver Riddletoy.func @main() {
1230050e8f0SRiver Riddle  %0 = toy.constant dense<[[1.000000e+00, 2.000000e+00, 3.000000e+00], [4.000000e+00, 5.000000e+00, 6.000000e+00]]> : tensor<2x3xf64>
1240050e8f0SRiver Riddle  %2 = toy.transpose(%0 : tensor<2x3xf64>) to tensor<3x2xf64>
1250050e8f0SRiver Riddle  %3 = toy.mul %2, %2 : tensor<3x2xf64>
1260050e8f0SRiver Riddle  toy.print %3 : tensor<3x2xf64>
1270050e8f0SRiver Riddle  toy.return
1285b4a01d4SMehdi Amini}
1295b4a01d4SMehdi Amini```
1305b4a01d4SMehdi Amini
1315b4a01d4SMehdi AminiWe can now lower down to the LLVM dialect, which produces the following code:
1325b4a01d4SMehdi Amini
1335b4a01d4SMehdi Amini```mlir
1345b4a01d4SMehdi Aminillvm.func @free(!llvm<"i8*">)
1352230bf99SAlex Zinenkollvm.func @printf(!llvm<"i8*">, ...) -> i32
1362230bf99SAlex Zinenkollvm.func @malloc(i64) -> !llvm<"i8*">
1375b4a01d4SMehdi Aminillvm.func @main() {
138dd5165a9SAlex Zinenko  %0 = llvm.mlir.constant(1.000000e+00 : f64) : f64
139dd5165a9SAlex Zinenko  %1 = llvm.mlir.constant(2.000000e+00 : f64) : f64
1405b4a01d4SMehdi Amini
1415b4a01d4SMehdi Amini  ...
1425b4a01d4SMehdi Amini
1435b4a01d4SMehdi Amini^bb16:
1445c5af910SJeff Niu  %221 = llvm.extractvalue %25[0] : !llvm<"{ double*, i64, [2 x i64], [2 x i64] }">
1452230bf99SAlex Zinenko  %222 = llvm.mlir.constant(0 : index) : i64
1462230bf99SAlex Zinenko  %223 = llvm.mlir.constant(2 : index) : i64
1472230bf99SAlex Zinenko  %224 = llvm.mul %214, %223 : i64
1482230bf99SAlex Zinenko  %225 = llvm.add %222, %224 : i64
1492230bf99SAlex Zinenko  %226 = llvm.mlir.constant(1 : index) : i64
1502230bf99SAlex Zinenko  %227 = llvm.mul %219, %226 : i64
1512230bf99SAlex Zinenko  %228 = llvm.add %225, %227 : i64
152dd5165a9SAlex Zinenko  %229 = llvm.getelementptr %221[%228] : (!llvm."double*">, i64) -> !llvm<"f64*">
1535b4a01d4SMehdi Amini  %230 = llvm.load %229 : !llvm<"double*">
154dd5165a9SAlex Zinenko  %231 = llvm.call @printf(%207, %230) : (!llvm<"i8*">, f64) -> i32
1552230bf99SAlex Zinenko  %232 = llvm.add %219, %218 : i64
1562230bf99SAlex Zinenko  llvm.br ^bb15(%232 : i64)
1575b4a01d4SMehdi Amini
1585b4a01d4SMehdi Amini  ...
1595b4a01d4SMehdi Amini
1605b4a01d4SMehdi Amini^bb18:
1615c5af910SJeff Niu  %235 = llvm.extractvalue %65[0] : !llvm<"{ double*, i64, [2 x i64], [2 x i64] }">
1625b4a01d4SMehdi Amini  %236 = llvm.bitcast %235 : !llvm<"double*"> to !llvm<"i8*">
1635b4a01d4SMehdi Amini  llvm.call @free(%236) : (!llvm<"i8*">) -> ()
1645c5af910SJeff Niu  %237 = llvm.extractvalue %45[0] : !llvm<"{ double*, i64, [2 x i64], [2 x i64] }">
1655b4a01d4SMehdi Amini  %238 = llvm.bitcast %237 : !llvm<"double*"> to !llvm<"i8*">
1665b4a01d4SMehdi Amini  llvm.call @free(%238) : (!llvm<"i8*">) -> ()
1675c5af910SJeff Niu  %239 = llvm.extractvalue %25[0] : !llvm<"{ double*, i64, [2 x i64], [2 x i64] }">
1685b4a01d4SMehdi Amini  %240 = llvm.bitcast %239 : !llvm<"double*"> to !llvm<"i8*">
1695b4a01d4SMehdi Amini  llvm.call @free(%240) : (!llvm<"i8*">) -> ()
1705b4a01d4SMehdi Amini  llvm.return
1715b4a01d4SMehdi Amini}
1725b4a01d4SMehdi Amini```
1735b4a01d4SMehdi Amini
174*5ad919a1SWheestSee [LLVM IR Target](../../TargetLLVMIR.md) for
1755b4a01d4SMehdi Aminimore in-depth details on lowering to the LLVM dialect.
1765b4a01d4SMehdi Amini
1771842fd50SJacques Pienaar## CodeGen: Getting Out of MLIR
1785b4a01d4SMehdi Amini
1795b4a01d4SMehdi AminiAt this point we are right at the cusp of code generation. We can generate code
1805b4a01d4SMehdi Aminiin the LLVM dialect, so now we just need to export to LLVM IR and setup a JIT to
1815b4a01d4SMehdi Aminirun it.
1825b4a01d4SMehdi Amini
1831842fd50SJacques Pienaar### Emitting LLVM IR
1845b4a01d4SMehdi Amini
1855b4a01d4SMehdi AminiNow that our module is comprised only of operations in the LLVM dialect, we can
1865b4a01d4SMehdi Aminiexport to LLVM IR. To do this programmatically, we can invoke the following
1875b4a01d4SMehdi Aminiutility:
1885b4a01d4SMehdi Amini
1895b4a01d4SMehdi Amini```c++
1905b4a01d4SMehdi Amini  std::unique_ptr<llvm::Module> llvmModule = mlir::translateModuleToLLVMIR(module);
1915b4a01d4SMehdi Amini  if (!llvmModule)
1925b4a01d4SMehdi Amini    /* ... an error was encountered ... */
1935b4a01d4SMehdi Amini```
1945b4a01d4SMehdi Amini
1955b4a01d4SMehdi AminiExporting our module to LLVM IR generates:
1965b4a01d4SMehdi Amini
197430bba2aSJacques Pienaar```llvm
1985b4a01d4SMehdi Aminidefine void @main() {
1995b4a01d4SMehdi Amini  ...
2005b4a01d4SMehdi Amini
2015b4a01d4SMehdi Amini102:
2025b4a01d4SMehdi Amini  %103 = extractvalue { double*, i64, [2 x i64], [2 x i64] } %8, 0
2035b4a01d4SMehdi Amini  %104 = mul i64 %96, 2
2045b4a01d4SMehdi Amini  %105 = add i64 0, %104
2055b4a01d4SMehdi Amini  %106 = mul i64 %100, 1
2065b4a01d4SMehdi Amini  %107 = add i64 %105, %106
2075b4a01d4SMehdi Amini  %108 = getelementptr double, double* %103, i64 %107
208a54f4eaeSMogball  %109 = memref.load double, double* %108
2095b4a01d4SMehdi Amini  %110 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @frmt_spec, i64 0, i64 0), double %109)
2105b4a01d4SMehdi Amini  %111 = add i64 %100, 1
211ace01605SRiver Riddle  cf.br label %99
2125b4a01d4SMehdi Amini
2135b4a01d4SMehdi Amini  ...
2145b4a01d4SMehdi Amini
2155b4a01d4SMehdi Amini115:
2165b4a01d4SMehdi Amini  %116 = extractvalue { double*, i64, [2 x i64], [2 x i64] } %24, 0
2175b4a01d4SMehdi Amini  %117 = bitcast double* %116 to i8*
2185b4a01d4SMehdi Amini  call void @free(i8* %117)
2195b4a01d4SMehdi Amini  %118 = extractvalue { double*, i64, [2 x i64], [2 x i64] } %16, 0
2205b4a01d4SMehdi Amini  %119 = bitcast double* %118 to i8*
2215b4a01d4SMehdi Amini  call void @free(i8* %119)
2225b4a01d4SMehdi Amini  %120 = extractvalue { double*, i64, [2 x i64], [2 x i64] } %8, 0
2235b4a01d4SMehdi Amini  %121 = bitcast double* %120 to i8*
2245b4a01d4SMehdi Amini  call void @free(i8* %121)
2255b4a01d4SMehdi Amini  ret void
2265b4a01d4SMehdi Amini}
2275b4a01d4SMehdi Amini```
2285b4a01d4SMehdi Amini
2295b4a01d4SMehdi AminiIf we enable optimization on the generated LLVM IR, we can trim this down quite
2305b4a01d4SMehdi Aminia bit:
2315b4a01d4SMehdi Amini
232430bba2aSJacques Pienaar```llvm
2335b4a01d4SMehdi Aminidefine void @main()
2345b4a01d4SMehdi Amini  %0 = tail call i32 (i8*, ...) @printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([4 x i8], [4 x i8]* @frmt_spec, i64 0, i64 0), double 1.000000e+00)
2355b4a01d4SMehdi Amini  %1 = tail call i32 (i8*, ...) @printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([4 x i8], [4 x i8]* @frmt_spec, i64 0, i64 0), double 1.600000e+01)
2365b4a01d4SMehdi Amini  %putchar = tail call i32 @putchar(i32 10)
2375b4a01d4SMehdi Amini  %2 = tail call i32 (i8*, ...) @printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([4 x i8], [4 x i8]* @frmt_spec, i64 0, i64 0), double 4.000000e+00)
2385b4a01d4SMehdi Amini  %3 = tail call i32 (i8*, ...) @printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([4 x i8], [4 x i8]* @frmt_spec, i64 0, i64 0), double 2.500000e+01)
2395b4a01d4SMehdi Amini  %putchar.1 = tail call i32 @putchar(i32 10)
2405b4a01d4SMehdi Amini  %4 = tail call i32 (i8*, ...) @printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([4 x i8], [4 x i8]* @frmt_spec, i64 0, i64 0), double 9.000000e+00)
2415b4a01d4SMehdi Amini  %5 = tail call i32 (i8*, ...) @printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([4 x i8], [4 x i8]* @frmt_spec, i64 0, i64 0), double 3.600000e+01)
2425b4a01d4SMehdi Amini  %putchar.2 = tail call i32 @putchar(i32 10)
2435b4a01d4SMehdi Amini  ret void
2445b4a01d4SMehdi Amini}
2455b4a01d4SMehdi Amini```
2465b4a01d4SMehdi Amini
247495cf272SLucy FoxThe full code listing for dumping LLVM IR can be found in
248495cf272SLucy Fox`examples/toy/Ch6/toy.cpp` in the `dumpLLVMIR()` function:
2495b4a01d4SMehdi Amini
2505b4a01d4SMehdi Amini```c++
2515b4a01d4SMehdi Amini
2525b4a01d4SMehdi Aminiint dumpLLVMIR(mlir::ModuleOp module) {
253db1c197bSAlex Zinenko  // Translate the module, that contains the LLVM dialect, to LLVM IR. Use a
254db1c197bSAlex Zinenko  // fresh LLVM IR context. (Note that LLVM is not thread-safe and any
255db1c197bSAlex Zinenko  // concurrent use of a context requires external locking.)
256db1c197bSAlex Zinenko  llvm::LLVMContext llvmContext;
257db1c197bSAlex Zinenko  auto llvmModule = mlir::translateModuleToLLVMIR(module, llvmContext);
2585b4a01d4SMehdi Amini  if (!llvmModule) {
2595b4a01d4SMehdi Amini    llvm::errs() << "Failed to emit LLVM IR\n";
2605b4a01d4SMehdi Amini    return -1;
2615b4a01d4SMehdi Amini  }
2625b4a01d4SMehdi Amini
2635b4a01d4SMehdi Amini  // Initialize LLVM targets.
2645b4a01d4SMehdi Amini  llvm::InitializeNativeTarget();
2655b4a01d4SMehdi Amini  llvm::InitializeNativeTargetAsmPrinter();
2665b4a01d4SMehdi Amini  mlir::ExecutionEngine::setupTargetTriple(llvmModule.get());
2675b4a01d4SMehdi Amini
2685b4a01d4SMehdi Amini  /// Optionally run an optimization pipeline over the llvm module.
2695b4a01d4SMehdi Amini  auto optPipeline = mlir::makeOptimizingTransformer(
2705b4a01d4SMehdi Amini      /*optLevel=*/EnableOpt ? 3 : 0, /*sizeLevel=*/0,
2715b4a01d4SMehdi Amini      /*targetMachine=*/nullptr);
2725b4a01d4SMehdi Amini  if (auto err = optPipeline(llvmModule.get())) {
2735b4a01d4SMehdi Amini    llvm::errs() << "Failed to optimize LLVM IR " << err << "\n";
2745b4a01d4SMehdi Amini    return -1;
2755b4a01d4SMehdi Amini  }
2765b4a01d4SMehdi Amini  llvm::errs() << *llvmModule << "\n";
2775b4a01d4SMehdi Amini  return 0;
2785b4a01d4SMehdi Amini}
2795b4a01d4SMehdi Amini```
2805b4a01d4SMehdi Amini
2811842fd50SJacques Pienaar### Setting up a JIT
2825b4a01d4SMehdi Amini
2835b4a01d4SMehdi AminiSetting up a JIT to run the module containing the LLVM dialect can be done using
2845b4a01d4SMehdi Aminithe `mlir::ExecutionEngine` infrastructure. This is a utility wrapper around
2855b4a01d4SMehdi AminiLLVM's JIT that accepts `.mlir` as input. The full code listing for setting up
2868928c6dbSMatthias Krammthe JIT can be found in `Ch6/toyc.cpp` in the `runJit()` function:
2875b4a01d4SMehdi Amini
2885b4a01d4SMehdi Amini```c++
2895b4a01d4SMehdi Aminiint runJit(mlir::ModuleOp module) {
2905b4a01d4SMehdi Amini  // Initialize LLVM targets.
2915b4a01d4SMehdi Amini  llvm::InitializeNativeTarget();
2925b4a01d4SMehdi Amini  llvm::InitializeNativeTargetAsmPrinter();
2935b4a01d4SMehdi Amini
2945b4a01d4SMehdi Amini  // An optimization pipeline to use within the execution engine.
2955b4a01d4SMehdi Amini  auto optPipeline = mlir::makeOptimizingTransformer(
2965b4a01d4SMehdi Amini      /*optLevel=*/EnableOpt ? 3 : 0, /*sizeLevel=*/0,
2975b4a01d4SMehdi Amini      /*targetMachine=*/nullptr);
2985b4a01d4SMehdi Amini
2995b4a01d4SMehdi Amini  // Create an MLIR execution engine. The execution engine eagerly JIT-compiles
3005b4a01d4SMehdi Amini  // the module.
3019c90725eSFrederik Gossen  auto maybeEngine = mlir::ExecutionEngine::create(module,
3029c90725eSFrederik Gossen      /*llvmModuleBuilder=*/nullptr, optPipeline);
3035b4a01d4SMehdi Amini  assert(maybeEngine && "failed to construct an execution engine");
3045b4a01d4SMehdi Amini  auto &engine = maybeEngine.get();
3055b4a01d4SMehdi Amini
3065b4a01d4SMehdi Amini  // Invoke the JIT-compiled function.
3075b4a01d4SMehdi Amini  auto invocationResult = engine->invoke("main");
3085b4a01d4SMehdi Amini  if (invocationResult) {
3095b4a01d4SMehdi Amini    llvm::errs() << "JIT invocation failed\n";
3105b4a01d4SMehdi Amini    return -1;
3115b4a01d4SMehdi Amini  }
3125b4a01d4SMehdi Amini
3135b4a01d4SMehdi Amini  return 0;
3145b4a01d4SMehdi Amini}
3155b4a01d4SMehdi Amini```
3165b4a01d4SMehdi Amini
3175b4a01d4SMehdi AminiYou can play around with it from the build directory:
3185b4a01d4SMehdi Amini
319430bba2aSJacques Pienaar```shell
3205b4a01d4SMehdi Amini$ echo 'def main() { print([[1, 2], [3, 4]]); }' | ./bin/toyc-ch6 -emit=jit
3215b4a01d4SMehdi Amini1.000000 2.000000
3225b4a01d4SMehdi Amini3.000000 4.000000
3235b4a01d4SMehdi Amini```
3245b4a01d4SMehdi Amini
3255b4a01d4SMehdi AminiYou can also play with `-emit=mlir`, `-emit=mlir-affine`, `-emit=mlir-llvm`, and
3265b4a01d4SMehdi Amini`-emit=llvm` to compare the various levels of IR involved. Also try options like
327fb16ed25SAndrzej Warzynski[`--mlir-print-ir-after-all`](../../PassManagement.md/#ir-printing) to track the
3285b4a01d4SMehdi Aminievolution of the IR throughout the pipeline.
3295b4a01d4SMehdi Amini
330495cf272SLucy FoxThe example code used throughout this section can be found in
331495cf272SLucy Foxtest/Examples/Toy/Ch6/llvm-lowering.mlir.
332495cf272SLucy Fox
3335b4a01d4SMehdi AminiSo far, we have worked with primitive data types. In the
3345b4a01d4SMehdi Amini[next chapter](Ch-7.md), we will add a composite `struct` type.
335