1 //===- toyc.cpp - The Toy Compiler ----------------------------------------===//
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 implements the entry point for the Toy compiler.
10 //
11 //===----------------------------------------------------------------------===//
12
13 #include "toy/Dialect.h"
14 #include "toy/MLIRGen.h"
15 #include "toy/Parser.h"
16 #include "toy/Passes.h"
17
18 #include "mlir/Dialect/Affine/Passes.h"
19 #include "mlir/ExecutionEngine/ExecutionEngine.h"
20 #include "mlir/ExecutionEngine/OptUtils.h"
21 #include "mlir/IR/AsmState.h"
22 #include "mlir/IR/BuiltinOps.h"
23 #include "mlir/IR/MLIRContext.h"
24 #include "mlir/IR/Verifier.h"
25 #include "mlir/InitAllDialects.h"
26 #include "mlir/Parser/Parser.h"
27 #include "mlir/Pass/Pass.h"
28 #include "mlir/Pass/PassManager.h"
29 #include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
30 #include "mlir/Target/LLVMIR/Export.h"
31 #include "mlir/Transforms/Passes.h"
32
33 #include "llvm/ADT/StringRef.h"
34 #include "llvm/IR/Module.h"
35 #include "llvm/Support/CommandLine.h"
36 #include "llvm/Support/ErrorOr.h"
37 #include "llvm/Support/MemoryBuffer.h"
38 #include "llvm/Support/SourceMgr.h"
39 #include "llvm/Support/TargetSelect.h"
40 #include "llvm/Support/raw_ostream.h"
41
42 using namespace toy;
43 namespace cl = llvm::cl;
44
45 static cl::opt<std::string> inputFilename(cl::Positional,
46 cl::desc("<input toy file>"),
47 cl::init("-"),
48 cl::value_desc("filename"));
49
50 namespace {
51 enum InputType { Toy, MLIR };
52 } // namespace
53 static cl::opt<enum InputType> inputType(
54 "x", cl::init(Toy), cl::desc("Decided the kind of output desired"),
55 cl::values(clEnumValN(Toy, "toy", "load the input file as a Toy source.")),
56 cl::values(clEnumValN(MLIR, "mlir",
57 "load the input file as an MLIR file")));
58
59 namespace {
60 enum Action {
61 None,
62 DumpAST,
63 DumpMLIR,
64 DumpMLIRAffine,
65 DumpMLIRLLVM,
66 DumpLLVMIR,
67 RunJIT
68 };
69 } // namespace
70 static cl::opt<enum Action> emitAction(
71 "emit", cl::desc("Select the kind of output desired"),
72 cl::values(clEnumValN(DumpAST, "ast", "output the AST dump")),
73 cl::values(clEnumValN(DumpMLIR, "mlir", "output the MLIR dump")),
74 cl::values(clEnumValN(DumpMLIRAffine, "mlir-affine",
75 "output the MLIR dump after affine lowering")),
76 cl::values(clEnumValN(DumpMLIRLLVM, "mlir-llvm",
77 "output the MLIR dump after llvm lowering")),
78 cl::values(clEnumValN(DumpLLVMIR, "llvm", "output the LLVM IR dump")),
79 cl::values(
80 clEnumValN(RunJIT, "jit",
81 "JIT the code and run it by invoking the main function")));
82
83 static cl::opt<bool> enableOpt("opt", cl::desc("Enable optimizations"));
84
85 /// Returns a Toy AST resulting from parsing the file or a nullptr on error.
parseInputFile(llvm::StringRef filename)86 std::unique_ptr<toy::ModuleAST> parseInputFile(llvm::StringRef filename) {
87 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> fileOrErr =
88 llvm::MemoryBuffer::getFileOrSTDIN(filename);
89 if (std::error_code ec = fileOrErr.getError()) {
90 llvm::errs() << "Could not open input file: " << ec.message() << "\n";
91 return nullptr;
92 }
93 auto buffer = fileOrErr.get()->getBuffer();
94 LexerBuffer lexer(buffer.begin(), buffer.end(), std::string(filename));
95 Parser parser(lexer);
96 return parser.parseModule();
97 }
98
loadMLIR(mlir::MLIRContext & context,mlir::OwningOpRef<mlir::ModuleOp> & module)99 int loadMLIR(mlir::MLIRContext &context,
100 mlir::OwningOpRef<mlir::ModuleOp> &module) {
101 // Handle '.toy' input to the compiler.
102 if (inputType != InputType::MLIR &&
103 !llvm::StringRef(inputFilename).endswith(".mlir")) {
104 auto moduleAST = parseInputFile(inputFilename);
105 if (!moduleAST)
106 return 6;
107 module = mlirGen(context, *moduleAST);
108 return !module ? 1 : 0;
109 }
110
111 // Otherwise, the input is '.mlir'.
112 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> fileOrErr =
113 llvm::MemoryBuffer::getFileOrSTDIN(inputFilename);
114 if (std::error_code ec = fileOrErr.getError()) {
115 llvm::errs() << "Could not open input file: " << ec.message() << "\n";
116 return -1;
117 }
118
119 // Parse the input mlir.
120 llvm::SourceMgr sourceMgr;
121 sourceMgr.AddNewSourceBuffer(std::move(*fileOrErr), llvm::SMLoc());
122 module = mlir::parseSourceFile<mlir::ModuleOp>(sourceMgr, &context);
123 if (!module) {
124 llvm::errs() << "Error can't load file " << inputFilename << "\n";
125 return 3;
126 }
127 return 0;
128 }
129
loadAndProcessMLIR(mlir::MLIRContext & context,mlir::OwningOpRef<mlir::ModuleOp> & module)130 int loadAndProcessMLIR(mlir::MLIRContext &context,
131 mlir::OwningOpRef<mlir::ModuleOp> &module) {
132 if (int error = loadMLIR(context, module))
133 return error;
134
135 mlir::PassManager pm(&context);
136 // Apply any generic pass manager command line options and run the pipeline.
137 applyPassManagerCLOptions(pm);
138
139 // Check to see what granularity of MLIR we are compiling to.
140 bool isLoweringToAffine = emitAction >= Action::DumpMLIRAffine;
141 bool isLoweringToLLVM = emitAction >= Action::DumpMLIRLLVM;
142
143 if (enableOpt || isLoweringToAffine) {
144 // Inline all functions into main and then delete them.
145 pm.addPass(mlir::createInlinerPass());
146
147 // Now that there is only one function, we can infer the shapes of each of
148 // the operations.
149 mlir::OpPassManager &optPM = pm.nest<mlir::toy::FuncOp>();
150 optPM.addPass(mlir::toy::createShapeInferencePass());
151 optPM.addPass(mlir::createCanonicalizerPass());
152 optPM.addPass(mlir::createCSEPass());
153 }
154
155 if (isLoweringToAffine) {
156 // Partially lower the toy dialect.
157 pm.addPass(mlir::toy::createLowerToAffinePass());
158
159 // Add a few cleanups post lowering.
160 mlir::OpPassManager &optPM = pm.nest<mlir::func::FuncOp>();
161 optPM.addPass(mlir::createCanonicalizerPass());
162 optPM.addPass(mlir::createCSEPass());
163
164 // Add optimizations if enabled.
165 if (enableOpt) {
166 optPM.addPass(mlir::createLoopFusionPass());
167 optPM.addPass(mlir::createAffineScalarReplacementPass());
168 }
169 }
170
171 if (isLoweringToLLVM) {
172 // Finish lowering the toy IR to the LLVM dialect.
173 pm.addPass(mlir::toy::createLowerToLLVMPass());
174 }
175
176 if (mlir::failed(pm.run(*module)))
177 return 4;
178 return 0;
179 }
180
dumpAST()181 int dumpAST() {
182 if (inputType == InputType::MLIR) {
183 llvm::errs() << "Can't dump a Toy AST when the input is MLIR\n";
184 return 5;
185 }
186
187 auto moduleAST = parseInputFile(inputFilename);
188 if (!moduleAST)
189 return 1;
190
191 dump(*moduleAST);
192 return 0;
193 }
194
dumpLLVMIR(mlir::ModuleOp module)195 int dumpLLVMIR(mlir::ModuleOp module) {
196 // Register the translation to LLVM IR with the MLIR context.
197 mlir::registerLLVMDialectTranslation(*module->getContext());
198
199 // Convert the module to LLVM IR in a new LLVM IR context.
200 llvm::LLVMContext llvmContext;
201 auto llvmModule = mlir::translateModuleToLLVMIR(module, llvmContext);
202 if (!llvmModule) {
203 llvm::errs() << "Failed to emit LLVM IR\n";
204 return -1;
205 }
206
207 // Initialize LLVM targets.
208 llvm::InitializeNativeTarget();
209 llvm::InitializeNativeTargetAsmPrinter();
210 mlir::ExecutionEngine::setupTargetTriple(llvmModule.get());
211
212 /// Optionally run an optimization pipeline over the llvm module.
213 auto optPipeline = mlir::makeOptimizingTransformer(
214 /*optLevel=*/enableOpt ? 3 : 0, /*sizeLevel=*/0,
215 /*targetMachine=*/nullptr);
216 if (auto err = optPipeline(llvmModule.get())) {
217 llvm::errs() << "Failed to optimize LLVM IR " << err << "\n";
218 return -1;
219 }
220 llvm::errs() << *llvmModule << "\n";
221 return 0;
222 }
223
runJit(mlir::ModuleOp module)224 int runJit(mlir::ModuleOp module) {
225 // Initialize LLVM targets.
226 llvm::InitializeNativeTarget();
227 llvm::InitializeNativeTargetAsmPrinter();
228
229 // Register the translation from MLIR to LLVM IR, which must happen before we
230 // can JIT-compile.
231 mlir::registerLLVMDialectTranslation(*module->getContext());
232
233 // An optimization pipeline to use within the execution engine.
234 auto optPipeline = mlir::makeOptimizingTransformer(
235 /*optLevel=*/enableOpt ? 3 : 0, /*sizeLevel=*/0,
236 /*targetMachine=*/nullptr);
237
238 // Create an MLIR execution engine. The execution engine eagerly JIT-compiles
239 // the module.
240 mlir::ExecutionEngineOptions engineOptions;
241 engineOptions.transformer = optPipeline;
242 auto maybeEngine = mlir::ExecutionEngine::create(module, engineOptions);
243 assert(maybeEngine && "failed to construct an execution engine");
244 auto &engine = maybeEngine.get();
245
246 // Invoke the JIT-compiled function.
247 auto invocationResult = engine->invokePacked("main");
248 if (invocationResult) {
249 llvm::errs() << "JIT invocation failed\n";
250 return -1;
251 }
252
253 return 0;
254 }
255
main(int argc,char ** argv)256 int main(int argc, char **argv) {
257 // Register any command line options.
258 mlir::registerAsmPrinterCLOptions();
259 mlir::registerMLIRContextCLOptions();
260 mlir::registerPassManagerCLOptions();
261
262 cl::ParseCommandLineOptions(argc, argv, "toy compiler\n");
263
264 if (emitAction == Action::DumpAST)
265 return dumpAST();
266
267 // If we aren't dumping the AST, then we are compiling with/to MLIR.
268
269 mlir::MLIRContext context;
270 // Load our Dialect in this MLIR Context.
271 context.getOrLoadDialect<mlir::toy::ToyDialect>();
272
273 mlir::OwningOpRef<mlir::ModuleOp> module;
274 if (int error = loadAndProcessMLIR(context, module))
275 return error;
276
277 // If we aren't exporting to non-mlir, then we are done.
278 bool isOutputingMLIR = emitAction <= Action::DumpMLIRLLVM;
279 if (isOutputingMLIR) {
280 module->dump();
281 return 0;
282 }
283
284 // Check to see if we are compiling to LLVM IR.
285 if (emitAction == Action::DumpLLVMIR)
286 return dumpLLVMIR(*module);
287
288 // Otherwise, we must be running the jit.
289 if (emitAction == Action::RunJIT)
290 return runJit(*module);
291
292 llvm::errs() << "No action specified (parsing only?), use -emit=<action>\n";
293 return -1;
294 }
295