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